/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "glink_core_if.h" #include "glink_private.h" #include "glink_xprt_if.h" #define NUM_EDGES 5 #define XPRT_NAME "smd_trans" #define SMD_DTR_SIG BIT(31) #define SMD_CTS_SIG BIT(30) #define SMD_CD_SIG BIT(29) #define SMD_RI_SIG BIT(28) /** * enum command_types - commands send/received from remote system * @CMD_OPEN: Channel open request * @CMD_OPEN_ACK: Response to @CMD_OPEN * @CMD_CLOSE: Channel close request * @CMD_CLOSE_ACK: Response to @CMD_CLOSE */ enum command_types { CMD_OPEN, CMD_OPEN_ACK, CMD_CLOSE, CMD_CLOSE_ACK, }; /* * Max of 64 channels, the 128 offset puts the rcid out of the * range the remote might use */ #define LEGACY_RCID_CHANNEL_OFFSET 128 #define SMDXPRT_ERR(einfo, x...) GLINK_XPRT_IF_ERR(einfo->xprt_if, x) #define SMDXPRT_INFO(einfo, x...) GLINK_XPRT_IF_INFO(einfo->xprt_if, x) #define SMDXPRT_DBG(einfo, x...) GLINK_XPRT_IF_DBG(einfo->xprt_if, x) /** * struct edge_info() - local information for managing an edge * @xprt_if: The transport interface registered with the glink code * associated with this edge. * @xprt_cfg: The transport configuration for the glink core * associated with this edge. * @smd_edge: The smd edge value corresponding to this edge. * @channels: A list of all the channels that currently exist on this * edge. * @channels_lock: Protects @channels "reads" from "writes". * @intentless: Flag indicating this edge is intentless. * @irq_disabled: Flag indicating whether interrupt is enabled or * disabled. * @ssr_sync: Synchronizes SSR with any ongoing activity that might * conflict. * @in_ssr: Prevents new activity that might conflict with an active * SSR. * @ssr_work: Ends SSR processing after giving SMD a chance to wrap up * SSR. * @smd_ch: Private SMD channel for channel migration. * @smd_lock: Serializes write access to @smd_ch. * @in_ssr_lock: Lock to protect the @in_ssr. * @smd_ctl_ch_open: Indicates that @smd_ch is fully open. * @work: Work item for processing migration data. * @rx_cmd_lock: The transport interface lock to notify about received * commands in a sequential manner. * * Each transport registered with the core is represented by a single instance * of this structure which allows for complete management of the transport. */ struct edge_info { struct glink_transport_if xprt_if; struct glink_core_transport_cfg xprt_cfg; uint32_t smd_edge; struct list_head channels; spinlock_t channels_lock; bool intentless; bool irq_disabled; struct srcu_struct ssr_sync; bool in_ssr; struct delayed_work ssr_work; smd_channel_t *smd_ch; struct mutex smd_lock; struct mutex in_ssr_lock; bool smd_ctl_ch_open; struct work_struct work; struct mutex rx_cmd_lock; }; /** * struct channel() - local information for managing a channel * @node: For chaining this channel on list for its edge. * @name: The name of this channel. * @lcid: The local channel id the core uses for this channel. * @rcid: The true remote channel id for this channel. * @ch_probe_lock: Lock to protect channel probe status. * @wait_for_probe: This channel is waiting for a probe from SMD. * @had_probed: This channel probed in the past and may skip probe. * @edge: Handle to the edge_info this channel is associated with. * @smd_ch: Handle to the underlying smd channel. * @intents: List of active intents on this channel. * @used_intents: List of consumed intents on this channel. * @intents_lock: Lock to protect @intents and @used_intents. * @next_intent_id: The next id to use for generated intents. * @wq: Handle for running tasks. * @work: Task to process received data. * @cur_intent: The current intent for received data. * @intent_req: Flag indicating if an intent has been requested for rx. * @is_closing: Flag indicating this channel is currently in the closing * state. * @local_legacy: The local side of the channel is in legacy mode. * @remote_legacy: The remote side of the channel is in legacy mode. * @rx_data_lock: Used to serialize RX data processing. * @streaming_ch: Indicates the underlying SMD channel is streaming type. * @tx_resume_needed: Indicates whether a tx_resume call should be triggered. */ struct channel { struct list_head node; char name[GLINK_NAME_SIZE]; uint32_t lcid; uint32_t rcid; struct mutex ch_probe_lock; bool wait_for_probe; bool had_probed; struct edge_info *edge; smd_channel_t *smd_ch; struct list_head intents; struct list_head used_intents; spinlock_t intents_lock; uint32_t next_intent_id; struct workqueue_struct *wq; struct work_struct work; struct intent_info *cur_intent; bool intent_req; bool is_closing; bool local_legacy; bool remote_legacy; size_t intent_req_size; spinlock_t rx_data_lock; bool streaming_ch; bool tx_resume_needed; }; /** * struct intent_info() - information for managing an intent * @node: Used for putting this intent in a list for its channel. * @llid: The local intent id the core uses to identify this intent. * @size: The size of the intent in bytes. */ struct intent_info { struct list_head node; uint32_t liid; size_t size; }; /** * struct channel_work() - a task to be processed for a specific channel * @ch: The channel associated with this task. * @iid: Intent id associated with this task, may not always be valid. * @work: The task to be processed. */ struct channel_work { struct channel *ch; uint32_t iid; struct work_struct work; }; /** * struct pdrvs - Tracks a platform driver and its use among channels * @node: For tracking in the pdrv_list. * @pdrv: The platform driver to track. */ struct pdrvs { struct list_head node; struct platform_driver pdrv; }; static uint32_t negotiate_features_v1(struct glink_transport_if *if_ptr, const struct glink_core_version *version, uint32_t features); static struct edge_info edge_infos[NUM_EDGES] = { { .xprt_cfg.edge = "dsps", .smd_edge = SMD_APPS_DSPS, }, { .xprt_cfg.edge = "lpass", .smd_edge = SMD_APPS_QDSP, }, { .xprt_cfg.edge = "mpss", .smd_edge = SMD_APPS_MODEM, }, { .xprt_cfg.edge = "wcnss", .smd_edge = SMD_APPS_WCNSS, }, { .xprt_cfg.edge = "rpm", .smd_edge = SMD_APPS_RPM, .intentless = true, }, }; static struct glink_core_version versions[] = { {1, 0x00, negotiate_features_v1}, }; static LIST_HEAD(pdrv_list); static DEFINE_MUTEX(pdrv_list_mutex); static void process_data_event(struct work_struct *work); static int add_platform_driver(struct channel *ch); static void smd_data_ch_close(struct channel *ch); /** * check_write_avail() - Check if there is space to to write on the smd channel, * and enable the read interrupt if there is not. * @check_fn: The function to use to check if there is space to write * @ch: The channel to check * * Return: 0 on success or standard Linux error codes. */ static int check_write_avail(int (*check_fn)(smd_channel_t *), struct channel *ch) { int rc = check_fn(ch->smd_ch); if (rc == 0) { ch->tx_resume_needed = true; smd_enable_read_intr(ch->smd_ch); rc = check_fn(ch->smd_ch); if (rc > 0) { ch->tx_resume_needed = false; smd_disable_read_intr(ch->smd_ch); } } return rc; } /** * process_ctl_event() - process a control channel event task * @work: The migration task to process. */ static void process_ctl_event(struct work_struct *work) { struct command { uint32_t cmd; uint32_t id; uint32_t priority; }; struct command cmd; struct edge_info *einfo; struct channel *ch; struct channel *temp_ch; int pkt_size; int read_avail; char name[GLINK_NAME_SIZE]; bool found; unsigned long flags; einfo = container_of(work, struct edge_info, work); mutex_lock(&einfo->in_ssr_lock); if (einfo->in_ssr) { einfo->in_ssr = false; einfo->xprt_if.glink_core_if_ptr->link_up(&einfo->xprt_if); } mutex_unlock(&einfo->in_ssr_lock); while (smd_read_avail(einfo->smd_ch)) { found = false; pkt_size = smd_cur_packet_size(einfo->smd_ch); read_avail = smd_read_avail(einfo->smd_ch); if (pkt_size != read_avail) continue; smd_read(einfo->smd_ch, &cmd, sizeof(cmd)); if (cmd.cmd == CMD_OPEN) { smd_read(einfo->smd_ch, name, GLINK_NAME_SIZE); SMDXPRT_INFO(einfo, "%s RX OPEN '%s'\n", __func__, name); spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) { if (!strcmp(name, ch->name)) { found = true; break; } } spin_unlock_irqrestore(&einfo->channels_lock, flags); if (!found) { ch = kzalloc(sizeof(*ch), GFP_KERNEL); if (!ch) { SMDXPRT_ERR(einfo, "%s: ch alloc failed\n", __func__); continue; } strlcpy(ch->name, name, GLINK_NAME_SIZE); ch->edge = einfo; mutex_init(&ch->ch_probe_lock); INIT_LIST_HEAD(&ch->intents); INIT_LIST_HEAD(&ch->used_intents); spin_lock_init(&ch->intents_lock); spin_lock_init(&ch->rx_data_lock); INIT_WORK(&ch->work, process_data_event); ch->wq = create_singlethread_workqueue( ch->name); if (!ch->wq) { SMDXPRT_ERR(einfo, "%s: ch wq create failed\n", __func__); kfree(ch); continue; } /* * Channel could have been added to the list by * someone else so scan again. Channel creation * is non-atomic, so unlock and recheck is * necessary */ temp_ch = ch; spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) if (!strcmp(name, ch->name)) { found = true; break; } if (!found) { ch = temp_ch; list_add_tail(&ch->node, &einfo->channels); spin_unlock_irqrestore( &einfo->channels_lock, flags); } else { spin_unlock_irqrestore( &einfo->channels_lock, flags); destroy_workqueue(temp_ch->wq); kfree(temp_ch); } } if (ch->remote_legacy) { SMDXPRT_DBG(einfo, "%s SMD Remote Open '%s'\n", __func__, name); cmd.cmd = CMD_OPEN_ACK; cmd.priority = SMD_TRANS_XPRT_ID; mutex_lock(&einfo->smd_lock); while (smd_write_avail(einfo->smd_ch) < sizeof(cmd)) msleep(20); smd_write(einfo->smd_ch, &cmd, sizeof(cmd)); mutex_unlock(&einfo->smd_lock); continue; } else { SMDXPRT_DBG(einfo, "%s G-Link Remote Open '%s'\n", __func__, name); } ch->rcid = cmd.id; mutex_lock(&einfo->rx_cmd_lock); einfo->xprt_if.glink_core_if_ptr->rx_cmd_ch_remote_open( &einfo->xprt_if, cmd.id, name, cmd.priority); mutex_unlock(&einfo->rx_cmd_lock); } else if (cmd.cmd == CMD_OPEN_ACK) { SMDXPRT_INFO(einfo, "%s RX OPEN ACK lcid %u; xprt_req %u\n", __func__, cmd.id, cmd.priority); spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) if (cmd.id == ch->lcid) { found = true; break; } spin_unlock_irqrestore(&einfo->channels_lock, flags); if (!found) { SMDXPRT_ERR(einfo, "%s No channel match %u\n", __func__, cmd.id); continue; } add_platform_driver(ch); mutex_lock(&einfo->rx_cmd_lock); einfo->xprt_if.glink_core_if_ptr->rx_cmd_ch_open_ack( &einfo->xprt_if, cmd.id, cmd.priority); mutex_unlock(&einfo->rx_cmd_lock); } else if (cmd.cmd == CMD_CLOSE) { SMDXPRT_INFO(einfo, "%s RX REMOTE CLOSE rcid %u\n", __func__, cmd.id); spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) if (cmd.id == ch->rcid) { found = true; break; } spin_unlock_irqrestore(&einfo->channels_lock, flags); if (!found) SMDXPRT_ERR(einfo, "%s no matching rcid %u\n", __func__, cmd.id); if (found && !ch->remote_legacy) { mutex_lock(&einfo->rx_cmd_lock); einfo->xprt_if.glink_core_if_ptr-> rx_cmd_ch_remote_close( &einfo->xprt_if, cmd.id); mutex_unlock(&einfo->rx_cmd_lock); } else { /* not found or a legacy channel */ SMDXPRT_INFO(einfo, "%s Sim RX CLOSE ACK lcid %u\n", __func__, cmd.id); cmd.cmd = CMD_CLOSE_ACK; mutex_lock(&einfo->smd_lock); while (smd_write_avail(einfo->smd_ch) < sizeof(cmd)) msleep(20); smd_write(einfo->smd_ch, &cmd, sizeof(cmd)); mutex_unlock(&einfo->smd_lock); } } else if (cmd.cmd == CMD_CLOSE_ACK) { int rcu_id; SMDXPRT_INFO(einfo, "%s RX CLOSE ACK lcid %u\n", __func__, cmd.id); spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) { if (cmd.id == ch->lcid) { found = true; break; } } spin_unlock_irqrestore(&einfo->channels_lock, flags); if (!found) { SMDXPRT_ERR(einfo, "%s LCID not found %u\n", __func__, cmd.id); continue; } rcu_id = srcu_read_lock(&einfo->ssr_sync); smd_data_ch_close(ch); srcu_read_unlock(&einfo->ssr_sync, rcu_id); mutex_lock(&einfo->rx_cmd_lock); einfo->xprt_if.glink_core_if_ptr->rx_cmd_ch_close_ack( &einfo->xprt_if, cmd.id); mutex_unlock(&einfo->rx_cmd_lock); } } } /** * ctl_ch_notify() - process an event from the smd channel for ch migration * @priv: The edge the event occurred on. * @event: The event to process */ static void ctl_ch_notify(void *priv, unsigned event) { struct edge_info *einfo = priv; switch (event) { case SMD_EVENT_DATA: schedule_work(&einfo->work); break; case SMD_EVENT_OPEN: einfo->smd_ctl_ch_open = true; break; case SMD_EVENT_CLOSE: einfo->smd_ctl_ch_open = false; break; } } static int ctl_ch_probe(struct platform_device *pdev) { int i; struct edge_info *einfo; int ret = 0; for (i = 0; i < NUM_EDGES; ++i) if (pdev->id == edge_infos[i].smd_edge) break; einfo = &edge_infos[i]; ret = smd_named_open_on_edge("GLINK_CTRL", einfo->smd_edge, &einfo->smd_ch, einfo, ctl_ch_notify); if (ret != 0) SMDXPRT_ERR(einfo, "%s Opening failed %d for %d:'GLINK_CTRL'\n", __func__, ret, einfo->smd_edge); return ret; } /** * ssr_work_func() - process the end of ssr * @work: The ssr task to finish. */ static void ssr_work_func(struct work_struct *work) { struct delayed_work *w; struct edge_info *einfo; w = container_of(work, struct delayed_work, work); einfo = container_of(w, struct edge_info, ssr_work); mutex_lock(&einfo->in_ssr_lock); if (einfo->in_ssr) { einfo->in_ssr = false; einfo->xprt_if.glink_core_if_ptr->link_up(&einfo->xprt_if); } mutex_unlock(&einfo->in_ssr_lock); } /** * process_tx_done() - process a tx done task * @work: The tx done task to process. */ static void process_tx_done(struct work_struct *work) { struct channel_work *ch_work; struct channel *ch; struct edge_info *einfo; uint32_t riid; ch_work = container_of(work, struct channel_work, work); ch = ch_work->ch; riid = ch_work->iid; einfo = ch->edge; kfree(ch_work); einfo->xprt_if.glink_core_if_ptr->rx_cmd_tx_done(&einfo->xprt_if, ch->rcid, riid, false); } /** * process_open_event() - process an open event task * @work: The open task to process. */ static void process_open_event(struct work_struct *work) { struct channel_work *ch_work; struct channel *ch; struct edge_info *einfo; int ret; ch_work = container_of(work, struct channel_work, work); ch = ch_work->ch; einfo = ch->edge; /* * The SMD client is supposed to already know its channel type, but we * are just a translation layer, so we need to dynamically detect the * channel type. */ ret = smd_write_segment_avail(ch->smd_ch); if (ret == -ENODEV) ch->streaming_ch = true; if (ch->remote_legacy || !ch->rcid) { ch->remote_legacy = true; ch->rcid = ch->lcid + LEGACY_RCID_CHANNEL_OFFSET; mutex_lock(&einfo->rx_cmd_lock); einfo->xprt_if.glink_core_if_ptr->rx_cmd_ch_remote_open( &einfo->xprt_if, ch->rcid, ch->name, SMD_TRANS_XPRT_ID); mutex_unlock(&einfo->rx_cmd_lock); } kfree(ch_work); } /** * process_close_event() - process a close event task * @work: The close task to process. */ static void process_close_event(struct work_struct *work) { struct channel_work *ch_work; struct channel *ch; struct edge_info *einfo; ch_work = container_of(work, struct channel_work, work); ch = ch_work->ch; einfo = ch->edge; kfree(ch_work); if (ch->remote_legacy) { mutex_lock(&einfo->rx_cmd_lock); einfo->xprt_if.glink_core_if_ptr->rx_cmd_ch_remote_close( &einfo->xprt_if, ch->rcid); mutex_unlock(&einfo->rx_cmd_lock); } ch->rcid = 0; } /** * process_status_event() - process a status event task * @work: The status task to process. */ static void process_status_event(struct work_struct *work) { struct channel_work *ch_work; struct channel *ch; struct edge_info *einfo; uint32_t sigs = 0; int set; ch_work = container_of(work, struct channel_work, work); ch = ch_work->ch; einfo = ch->edge; kfree(ch_work); set = smd_tiocmget(ch->smd_ch); if (set < 0) return; if (set & TIOCM_DTR) sigs |= SMD_DTR_SIG; if (set & TIOCM_RTS) sigs |= SMD_CTS_SIG; if (set & TIOCM_CD) sigs |= SMD_CD_SIG; if (set & TIOCM_RI) sigs |= SMD_RI_SIG; einfo->xprt_if.glink_core_if_ptr->rx_cmd_remote_sigs(&einfo->xprt_if, ch->rcid, sigs); } /** * process_reopen_event() - process a reopen ready event task * @work: The reopen ready task to process. */ static void process_reopen_event(struct work_struct *work) { struct channel_work *ch_work; struct channel *ch; struct edge_info *einfo; ch_work = container_of(work, struct channel_work, work); ch = ch_work->ch; einfo = ch->edge; kfree(ch_work); if (ch->remote_legacy) { mutex_lock(&einfo->rx_cmd_lock); einfo->xprt_if.glink_core_if_ptr->rx_cmd_ch_remote_close( &einfo->xprt_if, ch->rcid); mutex_unlock(&einfo->rx_cmd_lock); } if (ch->local_legacy) { mutex_lock(&einfo->rx_cmd_lock); einfo->xprt_if.glink_core_if_ptr->rx_cmd_ch_close_ack( &einfo->xprt_if, ch->lcid); mutex_unlock(&einfo->rx_cmd_lock); } } /** * process_data_event() - process a data event task * @work: The data task to process. */ static void process_data_event(struct work_struct *work) { struct channel *ch; struct edge_info *einfo; struct glink_core_rx_intent *intent; int pkt_remaining; int read_avail; struct intent_info *i; uint32_t liid; unsigned long intents_flags; unsigned long rx_data_flags; ch = container_of(work, struct channel, work); einfo = ch->edge; if (ch->tx_resume_needed && smd_write_avail(ch->smd_ch) > 0) { ch->tx_resume_needed = false; smd_disable_read_intr(ch->smd_ch); einfo->xprt_if.glink_core_if_ptr->tx_resume(&einfo->xprt_if); } spin_lock_irqsave(&ch->rx_data_lock, rx_data_flags); while (!ch->is_closing && smd_read_avail(ch->smd_ch)) { if (!ch->streaming_ch) pkt_remaining = smd_cur_packet_size(ch->smd_ch); else pkt_remaining = smd_read_avail(ch->smd_ch); SMDXPRT_DBG(einfo, "%s Reading packet chunk %u '%s' %u:%u\n", __func__, pkt_remaining, ch->name, ch->lcid, ch->rcid); if (!ch->cur_intent && !einfo->intentless) { spin_lock_irqsave(&ch->intents_lock, intents_flags); ch->intent_req = true; ch->intent_req_size = pkt_remaining; list_for_each_entry(i, &ch->intents, node) { if (i->size >= pkt_remaining) { list_del(&i->node); ch->cur_intent = i; ch->intent_req = false; break; } } spin_unlock_irqrestore(&ch->intents_lock, intents_flags); if (!ch->cur_intent) { spin_unlock_irqrestore(&ch->rx_data_lock, rx_data_flags); SMDXPRT_DBG(einfo, "%s Reqesting intent '%s' %u:%u\n", __func__, ch->name, ch->lcid, ch->rcid); einfo->xprt_if.glink_core_if_ptr-> rx_cmd_remote_rx_intent_req( &einfo->xprt_if, ch->rcid, pkt_remaining); return; } } liid = einfo->intentless ? 0 : ch->cur_intent->liid; read_avail = smd_read_avail(ch->smd_ch); if (ch->streaming_ch && read_avail > pkt_remaining) read_avail = pkt_remaining; intent = einfo->xprt_if.glink_core_if_ptr->rx_get_pkt_ctx( &einfo->xprt_if, ch->rcid, liid); if (!intent->data && einfo->intentless) { intent->data = kmalloc(pkt_remaining, GFP_ATOMIC); if (!intent->data) { SMDXPRT_DBG(einfo, "%s kmalloc failed '%s' %u:%u\n", __func__, ch->name, ch->lcid, ch->rcid); continue; } } smd_read(ch->smd_ch, intent->data + intent->write_offset, read_avail); spin_unlock_irqrestore(&ch->rx_data_lock, rx_data_flags); intent->write_offset += read_avail; intent->pkt_size += read_avail; if (read_avail == pkt_remaining && !einfo->intentless) { spin_lock_irqsave(&ch->intents_lock, intents_flags); list_add_tail(&ch->cur_intent->node, &ch->used_intents); spin_unlock_irqrestore(&ch->intents_lock, intents_flags); ch->cur_intent = NULL; } einfo->xprt_if.glink_core_if_ptr->rx_put_pkt_ctx( &einfo->xprt_if, ch->rcid, intent, read_avail == pkt_remaining); spin_lock_irqsave(&ch->rx_data_lock, rx_data_flags); } spin_unlock_irqrestore(&ch->rx_data_lock, rx_data_flags); } /** * smd_data_ch_notify() - process an event from the smd channel * @priv: The channel the event occurred on. * @event: The event to process */ static void smd_data_ch_notify(void *priv, unsigned event) { struct channel *ch = priv; struct channel_work *work; switch (event) { case SMD_EVENT_DATA: queue_work(ch->wq, &ch->work); break; case SMD_EVENT_OPEN: work = kmalloc(sizeof(*work), GFP_ATOMIC); if (!work) { SMDXPRT_ERR(ch->edge, "%s: unable to process event %d\n", __func__, SMD_EVENT_OPEN); return; } work->ch = ch; INIT_WORK(&work->work, process_open_event); queue_work(ch->wq, &work->work); break; case SMD_EVENT_CLOSE: work = kmalloc(sizeof(*work), GFP_ATOMIC); if (!work) { SMDXPRT_ERR(ch->edge, "%s: unable to process event %d\n", __func__, SMD_EVENT_CLOSE); return; } work->ch = ch; INIT_WORK(&work->work, process_close_event); queue_work(ch->wq, &work->work); break; case SMD_EVENT_STATUS: SMDXPRT_DBG(ch->edge, "%s Processing STATUS for '%s' %u:%u\n", __func__, ch->name, ch->lcid, ch->rcid); work = kmalloc(sizeof(*work), GFP_ATOMIC); if (!work) { SMDXPRT_ERR(ch->edge, "%s: unable to process event %d\n", __func__, SMD_EVENT_STATUS); return; } work->ch = ch; INIT_WORK(&work->work, process_status_event); queue_work(ch->wq, &work->work); break; case SMD_EVENT_REOPEN_READY: work = kmalloc(sizeof(*work), GFP_ATOMIC); if (!work) { SMDXPRT_ERR(ch->edge, "%s: unable to process event %d\n", __func__, SMD_EVENT_REOPEN_READY); return; } work->ch = ch; INIT_WORK(&work->work, process_reopen_event); queue_work(ch->wq, &work->work); break; } } /** * smd_data_ch_close() - close and cleanup SMD data channel * @ch: Channel to cleanup * * Must be called with einfo->ssr_sync SRCU locked. */ static void smd_data_ch_close(struct channel *ch) { struct intent_info *intent; unsigned long flags; SMDXPRT_INFO(ch->edge, "%s Closing SMD channel lcid %u\n", __func__, ch->lcid); ch->is_closing = true; ch->tx_resume_needed = false; flush_workqueue(ch->wq); mutex_lock(&ch->ch_probe_lock); ch->wait_for_probe = false; if (ch->smd_ch) { smd_close(ch->smd_ch); ch->smd_ch = NULL; } else if (ch->local_legacy) { mutex_lock(&ch->edge->rx_cmd_lock); ch->edge->xprt_if.glink_core_if_ptr->rx_cmd_ch_close_ack( &ch->edge->xprt_if, ch->lcid); mutex_unlock(&ch->edge->rx_cmd_lock); } mutex_unlock(&ch->ch_probe_lock); ch->local_legacy = false; spin_lock_irqsave(&ch->intents_lock, flags); while (!list_empty(&ch->intents)) { intent = list_first_entry(&ch->intents, struct intent_info, node); list_del(&intent->node); kfree(intent); } while (!list_empty(&ch->used_intents)) { intent = list_first_entry(&ch->used_intents, struct intent_info, node); list_del(&intent->node); kfree(intent); } spin_unlock_irqrestore(&ch->intents_lock, flags); ch->is_closing = false; } static void data_ch_probe_body(struct channel *ch) { struct edge_info *einfo; int ret; einfo = ch->edge; SMDXPRT_DBG(einfo, "%s Opening SMD channel %d:'%s'\n", __func__, einfo->smd_edge, ch->name); ret = smd_named_open_on_edge(ch->name, einfo->smd_edge, &ch->smd_ch, ch, smd_data_ch_notify); if (ret != 0) { SMDXPRT_ERR(einfo, "%s Opening failed %d for %d:'%s'\n", __func__, ret, einfo->smd_edge, ch->name); return; } smd_disable_read_intr(ch->smd_ch); } static int channel_probe(struct platform_device *pdev) { struct channel *ch; struct edge_info *einfo; int i; bool found = false; unsigned long flags; for (i = 0; i < NUM_EDGES; ++i) { if (edge_infos[i].smd_edge == pdev->id) { found = true; break; } } if (!found) return -EPROBE_DEFER; einfo = &edge_infos[i]; found = false; spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) { if (!strcmp(pdev->name, ch->name)) { found = true; break; } } spin_unlock_irqrestore(&einfo->channels_lock, flags); if (!found) return -EPROBE_DEFER; mutex_lock(&ch->ch_probe_lock); if (!ch->wait_for_probe) { mutex_unlock(&ch->ch_probe_lock); return -EPROBE_DEFER; } ch->wait_for_probe = false; ch->had_probed = true; data_ch_probe_body(ch); mutex_unlock(&ch->ch_probe_lock); return 0; } static int dummy_probe(struct platform_device *pdev) { return 0; } static struct platform_driver dummy_driver = { .probe = dummy_probe, .driver = { .name = "dummydriver12345", .owner = THIS_MODULE, }, }; static struct platform_device dummy_device = { .name = "dummydriver12345", }; /** * add_platform_driver() - register the needed platform driver for a channel * @ch: The channel that needs a platform driver registered. * * SMD channels are unique by name/edge tuples, but the platform driver can * only specify the name of the channel, so multiple unique SMD channels can * be covered under one platform driver. Therfore we need to smartly manage * the muxing of channels on platform drivers. * * Return: Success or standard linux error code. */ static int add_platform_driver(struct channel *ch) { struct pdrvs *pdrv; bool found = false; int ret = 0; static bool first = true; mutex_lock(&pdrv_list_mutex); mutex_lock(&ch->ch_probe_lock); ch->wait_for_probe = true; list_for_each_entry(pdrv, &pdrv_list, node) { if (!strcmp(ch->name, pdrv->pdrv.driver.name)) { found = true; break; } } if (!found) { mutex_unlock(&ch->ch_probe_lock); pdrv = kzalloc(sizeof(*pdrv), GFP_KERNEL); if (!pdrv) { ret = -ENOMEM; mutex_lock(&ch->ch_probe_lock); ch->wait_for_probe = false; mutex_unlock(&ch->ch_probe_lock); goto out; } pdrv->pdrv.driver.name = ch->name; pdrv->pdrv.driver.owner = THIS_MODULE; pdrv->pdrv.probe = channel_probe; list_add_tail(&pdrv->node, &pdrv_list); ret = platform_driver_register(&pdrv->pdrv); if (ret) { list_del(&pdrv->node); kfree(pdrv); mutex_lock(&ch->ch_probe_lock); ch->wait_for_probe = false; mutex_unlock(&ch->ch_probe_lock); } } else { if (ch->had_probed) data_ch_probe_body(ch); mutex_unlock(&ch->ch_probe_lock); /* * channel_probe might have seen the device we want, but * returned EPROBE_DEFER so we need to kick the deferred list */ platform_driver_register(&dummy_driver); if (first) { platform_device_register(&dummy_device); first = false; } platform_driver_unregister(&dummy_driver); } out: mutex_unlock(&pdrv_list_mutex); return ret; } /** * tx_cmd_version() - convert a version cmd to wire format and transmit * @if_ptr: The transport to transmit on. * @version: The version number to encode. * @features: The features information to encode. * * The remote side doesn't speak G-Link, so we fake the version negotiation. */ static void tx_cmd_version(struct glink_transport_if *if_ptr, uint32_t version, uint32_t features) { struct edge_info *einfo; einfo = container_of(if_ptr, struct edge_info, xprt_if); einfo->xprt_if.glink_core_if_ptr->rx_cmd_version_ack(&einfo->xprt_if, version, features); einfo->xprt_if.glink_core_if_ptr->rx_cmd_version(&einfo->xprt_if, version, features); } /** * tx_cmd_version_ack() - convert a version ack cmd to wire format and transmit * @if_ptr: The transport to transmit on. * @version: The version number to encode. * @features: The features information to encode. * * The remote side doesn't speak G-Link. The core is acking a version command * we faked. Do nothing. */ static void tx_cmd_version_ack(struct glink_transport_if *if_ptr, uint32_t version, uint32_t features) { } /** * set_version() - activate a negotiated version and feature set * @if_ptr: The transport to configure. * @version: The version to use. * @features: The features to use. * * Return: The supported capabilities of the transport. */ static uint32_t set_version(struct glink_transport_if *if_ptr, uint32_t version, uint32_t features) { struct edge_info *einfo; uint32_t capabilities = GCAP_SIGNALS | GCAP_AUTO_QUEUE_RX_INT; einfo = container_of(if_ptr, struct edge_info, xprt_if); return einfo->intentless ? GCAP_INTENTLESS | capabilities : capabilities; } /** * tx_cmd_ch_open() - convert a channel open cmd to wire format and transmit * @if_ptr: The transport to transmit on. * @lcid: The local channel id to encode. * @name: The channel name to encode. * @req_xprt: The transport the core would like to migrate this channel to. * * Return: 0 on success or standard Linux error code. */ static int tx_cmd_ch_open(struct glink_transport_if *if_ptr, uint32_t lcid, const char *name, uint16_t req_xprt) { struct command { uint32_t cmd; uint32_t id; uint32_t priority; }; struct command cmd; struct edge_info *einfo; struct channel *ch; struct channel *temp_ch; bool found = false; int rcu_id; int ret = 0; int len; unsigned long flags; einfo = container_of(if_ptr, struct edge_info, xprt_if); rcu_id = srcu_read_lock(&einfo->ssr_sync); if (einfo->in_ssr) { srcu_read_unlock(&einfo->ssr_sync, rcu_id); return -EFAULT; } spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) { if (!strcmp(name, ch->name)) { found = true; break; } } spin_unlock_irqrestore(&einfo->channels_lock, flags); if (!found) { ch = kzalloc(sizeof(*ch), GFP_KERNEL); if (!ch) { SMDXPRT_ERR(einfo, "%s: channel struct allocation failed\n", __func__); srcu_read_unlock(&einfo->ssr_sync, rcu_id); return -ENOMEM; } strlcpy(ch->name, name, GLINK_NAME_SIZE); ch->edge = einfo; mutex_init(&ch->ch_probe_lock); INIT_LIST_HEAD(&ch->intents); INIT_LIST_HEAD(&ch->used_intents); spin_lock_init(&ch->intents_lock); spin_lock_init(&ch->rx_data_lock); INIT_WORK(&ch->work, process_data_event); ch->wq = create_singlethread_workqueue(ch->name); if (!ch->wq) { SMDXPRT_ERR(einfo, "%s: channel workqueue create failed\n", __func__); kfree(ch); srcu_read_unlock(&einfo->ssr_sync, rcu_id); return -ENOMEM; } /* * Channel could have been added to the list by someone else * so scan again. Channel creation is non-atomic, so unlock * and recheck is necessary */ temp_ch = ch; spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) if (!strcmp(name, ch->name)) { found = true; break; } if (!found) { ch = temp_ch; list_add_tail(&ch->node, &einfo->channels); spin_unlock_irqrestore(&einfo->channels_lock, flags); } else { spin_unlock_irqrestore(&einfo->channels_lock, flags); destroy_workqueue(temp_ch->wq); kfree(temp_ch); } } ch->tx_resume_needed = false; ch->lcid = lcid; if (einfo->smd_ctl_ch_open) { SMDXPRT_INFO(einfo, "%s TX OPEN '%s' lcid %u reqxprt %u\n", __func__, name, lcid, req_xprt); cmd.cmd = CMD_OPEN; cmd.id = lcid; cmd.priority = req_xprt; len = strlen(name) + 1; len += sizeof(cmd); mutex_lock(&einfo->smd_lock); while (smd_write_avail(einfo->smd_ch) < len) msleep(20); smd_write_start(einfo->smd_ch, len); smd_write_segment(einfo->smd_ch, &cmd, sizeof(cmd)); smd_write_segment(einfo->smd_ch, name, strlen(name) + 1); smd_write_end(einfo->smd_ch); mutex_unlock(&einfo->smd_lock); } else { SMDXPRT_INFO(einfo, "%s Legacy Open '%s' lcid %u reqxprt %u\n", __func__, name, lcid, req_xprt); ch->rcid = lcid + LEGACY_RCID_CHANNEL_OFFSET; ch->local_legacy = true; ch->remote_legacy = true; ret = add_platform_driver(ch); if (!ret) { mutex_lock(&einfo->rx_cmd_lock); einfo->xprt_if.glink_core_if_ptr->rx_cmd_ch_open_ack( &einfo->xprt_if, ch->lcid, SMD_TRANS_XPRT_ID); mutex_unlock(&einfo->rx_cmd_lock); } } srcu_read_unlock(&einfo->ssr_sync, rcu_id); return ret; } /** * tx_cmd_ch_close() - convert a channel close cmd to wire format and transmit * @if_ptr: The transport to transmit on. * @lcid: The local channel id to encode. * * Return: 0 on success or standard Linux error code. */ static int tx_cmd_ch_close(struct glink_transport_if *if_ptr, uint32_t lcid) { struct command { uint32_t cmd; uint32_t id; uint32_t reserved; }; struct command cmd; struct edge_info *einfo; struct channel *ch; int rcu_id; bool found = false; unsigned long flags; einfo = container_of(if_ptr, struct edge_info, xprt_if); rcu_id = srcu_read_lock(&einfo->ssr_sync); if (einfo->in_ssr) { srcu_read_unlock(&einfo->ssr_sync, rcu_id); return -EFAULT; } spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) if (lcid == ch->lcid) { found = true; break; } spin_unlock_irqrestore(&einfo->channels_lock, flags); if (!found) { SMDXPRT_ERR(einfo, "%s LCID not found %u\n", __func__, lcid); srcu_read_unlock(&einfo->ssr_sync, rcu_id); return -ENODEV; } if (!ch->local_legacy) { SMDXPRT_INFO(einfo, "%s TX CLOSE lcid %u\n", __func__, lcid); cmd.cmd = CMD_CLOSE; cmd.id = lcid; cmd.reserved = 0; mutex_lock(&einfo->smd_lock); while (smd_write_avail(einfo->smd_ch) < sizeof(cmd)) msleep(20); smd_write(einfo->smd_ch, &cmd, sizeof(cmd)); mutex_unlock(&einfo->smd_lock); } else { smd_data_ch_close(ch); } srcu_read_unlock(&einfo->ssr_sync, rcu_id); return 0; } /** * tx_cmd_ch_remote_open_ack() - convert a channel open ack cmd to wire format * and transmit * @if_ptr: The transport to transmit on. * @rcid: The remote channel id to encode. * @xprt_resp: The response to a transport migration request. */ static void tx_cmd_ch_remote_open_ack(struct glink_transport_if *if_ptr, uint32_t rcid, uint16_t xprt_resp) { struct command { uint32_t cmd; uint32_t id; uint32_t priority; }; struct command cmd; struct edge_info *einfo; struct channel *ch; bool found = false; unsigned long flags; einfo = container_of(if_ptr, struct edge_info, xprt_if); if (!einfo->smd_ctl_ch_open) return; spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) if (ch->rcid == rcid) { found = true; break; } spin_unlock_irqrestore(&einfo->channels_lock, flags); if (!found) { SMDXPRT_ERR(einfo, "%s No matching SMD channel for rcid %u\n", __func__, rcid); return; } if (ch->remote_legacy) { SMDXPRT_INFO(einfo, "%s Legacy ch rcid %u xprt_resp %u\n", __func__, rcid, xprt_resp); return; } SMDXPRT_INFO(einfo, "%s TX OPEN ACK rcid %u xprt_resp %u\n", __func__, rcid, xprt_resp); cmd.cmd = CMD_OPEN_ACK; cmd.id = ch->rcid; cmd.priority = xprt_resp; mutex_lock(&einfo->smd_lock); while (smd_write_avail(einfo->smd_ch) < sizeof(cmd)) msleep(20); smd_write(einfo->smd_ch, &cmd, sizeof(cmd)); mutex_unlock(&einfo->smd_lock); } /** * tx_cmd_ch_remote_close_ack() - convert a channel close ack cmd to wire format * and transmit * @if_ptr: The transport to transmit on. * @rcid: The remote channel id to encode. */ static void tx_cmd_ch_remote_close_ack(struct glink_transport_if *if_ptr, uint32_t rcid) { struct command { uint32_t cmd; uint32_t id; uint32_t reserved; }; struct command cmd; struct edge_info *einfo; struct channel *ch; bool found = false; unsigned long flags; einfo = container_of(if_ptr, struct edge_info, xprt_if); spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) if (rcid == ch->rcid) { found = true; break; } spin_unlock_irqrestore(&einfo->channels_lock, flags); if (!found) { SMDXPRT_ERR(einfo, "%s No matching SMD channel for rcid %u\n", __func__, rcid); return; } if (!ch->remote_legacy) { SMDXPRT_INFO(einfo, "%s TX CLOSE ACK rcid %u\n", __func__, rcid); cmd.cmd = CMD_CLOSE_ACK; cmd.id = rcid; cmd.reserved = 0; mutex_lock(&einfo->smd_lock); while (smd_write_avail(einfo->smd_ch) < sizeof(cmd)) msleep(20); smd_write(einfo->smd_ch, &cmd, sizeof(cmd)); mutex_unlock(&einfo->smd_lock); } ch->remote_legacy = false; ch->rcid = 0; } /** * ssr() - process a subsystem restart notification of a transport * @if_ptr: The transport to restart. * * Return: 0 on success or standard Linux error code. */ static int ssr(struct glink_transport_if *if_ptr) { struct edge_info *einfo; struct channel *ch; struct intent_info *intent; unsigned long flags; einfo = container_of(if_ptr, struct edge_info, xprt_if); einfo->in_ssr = true; synchronize_srcu(&einfo->ssr_sync); einfo->smd_ctl_ch_open = false; spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) { spin_unlock_irqrestore(&einfo->channels_lock, flags); ch->is_closing = true; flush_workqueue(ch->wq); mutex_lock(&ch->ch_probe_lock); ch->wait_for_probe = false; if (ch->smd_ch) { smd_close(ch->smd_ch); ch->smd_ch = NULL; } mutex_unlock(&ch->ch_probe_lock); ch->local_legacy = false; ch->remote_legacy = false; ch->rcid = 0; ch->tx_resume_needed = false; spin_lock_irqsave(&ch->intents_lock, flags); while (!list_empty(&ch->intents)) { intent = list_first_entry(&ch->intents, struct intent_info, node); list_del(&intent->node); kfree(intent); } while (!list_empty(&ch->used_intents)) { intent = list_first_entry(&ch->used_intents, struct intent_info, node); list_del(&intent->node); kfree(intent); } kfree(ch->cur_intent); ch->cur_intent = NULL; spin_unlock_irqrestore(&ch->intents_lock, flags); ch->is_closing = false; spin_lock_irqsave(&einfo->channels_lock, flags); } spin_unlock_irqrestore(&einfo->channels_lock, flags); einfo->xprt_if.glink_core_if_ptr->link_down(&einfo->xprt_if); schedule_delayed_work(&einfo->ssr_work, 5 * HZ); return 0; } /** * allocate_rx_intent() - allocate/reserve space for RX Intent * @if_ptr: The transport the intent is associated with. * @size: size of intent. * @intent: Pointer to the intent structure. * * Assign "data" with the buffer created, since the transport creates * a linear buffer and "iovec" with the "intent" itself, so that * the data can be passed to a client that receives only vector buffer. * Note that returning NULL for the pointer is valid (it means that space has * been reserved, but the actual pointer will be provided later). * * Return: 0 on success or standard Linux error code. */ static int allocate_rx_intent(struct glink_transport_if *if_ptr, size_t size, struct glink_core_rx_intent *intent) { void *t; t = kmalloc(size, GFP_KERNEL); if (!t) return -ENOMEM; intent->data = t; intent->iovec = (void *)intent; intent->vprovider = rx_linear_vbuf_provider; intent->pprovider = NULL; return 0; } /** * deallocate_rx_intent() - Deallocate space created for RX Intent * @if_ptr: The transport the intent is associated with. * @intent: Pointer to the intent structure. * * Return: 0 on success or standard Linux error code. */ static int deallocate_rx_intent(struct glink_transport_if *if_ptr, struct glink_core_rx_intent *intent) { if (!intent || !intent->data) return -EINVAL; kfree(intent->data); intent->data = NULL; intent->iovec = NULL; intent->vprovider = NULL; return 0; } /** * check_and_resume_rx() - Check the RX state and resume it * @ch: Channel which needs to be checked. * @intent_size: Intent size being queued. * * This function checks if a receive intent is requested in the * channel and resumes the RX if the queued receive intent satisifes * the requested receive intent. This function must be called with * ch->intents_lock locked. */ static void check_and_resume_rx(struct channel *ch, size_t intent_size) { if (ch->intent_req && ch->intent_req_size <= intent_size) { ch->intent_req = false; queue_work(ch->wq, &ch->work); } } /** * tx_cmd_local_rx_intent() - convert an rx intent cmd to wire format and * transmit * @if_ptr: The transport to transmit on. * @lcid: The local channel id to encode. * @size: The intent size to encode. * @liid: The local intent id to encode. * * Return: 0 on success or standard Linux error code. */ static int tx_cmd_local_rx_intent(struct glink_transport_if *if_ptr, uint32_t lcid, size_t size, uint32_t liid) { struct edge_info *einfo; struct channel *ch; struct intent_info *intent; int rcu_id; unsigned long flags; einfo = container_of(if_ptr, struct edge_info, xprt_if); rcu_id = srcu_read_lock(&einfo->ssr_sync); if (einfo->in_ssr) { srcu_read_unlock(&einfo->ssr_sync, rcu_id); return -EFAULT; } spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) { if (lcid == ch->lcid) break; } spin_unlock_irqrestore(&einfo->channels_lock, flags); intent = kmalloc(sizeof(*intent), GFP_KERNEL); if (!intent) { SMDXPRT_ERR(einfo, "%s: no memory for intent\n", __func__); srcu_read_unlock(&einfo->ssr_sync, rcu_id); return -ENOMEM; } intent->liid = liid; intent->size = size; spin_lock_irqsave(&ch->intents_lock, flags); list_add_tail(&intent->node, &ch->intents); check_and_resume_rx(ch, size); spin_unlock_irqrestore(&ch->intents_lock, flags); srcu_read_unlock(&einfo->ssr_sync, rcu_id); return 0; } /** * tx_cmd_local_rx_done() - convert an rx done cmd to wire format and transmit * @if_ptr: The transport to transmit on. * @lcid: The local channel id to encode. * @liid: The local intent id to encode. * @reuse: Reuse the consumed intent. */ static void tx_cmd_local_rx_done(struct glink_transport_if *if_ptr, uint32_t lcid, uint32_t liid, bool reuse) { struct edge_info *einfo; struct channel *ch; struct intent_info *i; unsigned long flags; einfo = container_of(if_ptr, struct edge_info, xprt_if); spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) { if (lcid == ch->lcid) break; } spin_unlock_irqrestore(&einfo->channels_lock, flags); spin_lock_irqsave(&ch->intents_lock, flags); list_for_each_entry(i, &ch->used_intents, node) { if (i->liid == liid) { list_del(&i->node); if (reuse) { list_add_tail(&i->node, &ch->intents); check_and_resume_rx(ch, i->size); } else { kfree(i); } break; } } spin_unlock_irqrestore(&ch->intents_lock, flags); } /** * tx() - convert a data transmit cmd to wire format and transmit * @if_ptr: The transport to transmit on. * @lcid: The local channel id to encode. * @pctx: The data to encode. * * Return: Number of bytes written or standard Linux error code. */ static int tx(struct glink_transport_if *if_ptr, uint32_t lcid, struct glink_core_tx_pkt *pctx) { struct edge_info *einfo; struct channel *ch; int rc; struct channel_work *tx_done; const void *data_start; size_t tx_size = 0; int rcu_id; unsigned long flags; einfo = container_of(if_ptr, struct edge_info, xprt_if); rcu_id = srcu_read_lock(&einfo->ssr_sync); if (einfo->in_ssr) { srcu_read_unlock(&einfo->ssr_sync, rcu_id); return -EFAULT; } spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) { if (lcid == ch->lcid) break; } spin_unlock_irqrestore(&einfo->channels_lock, flags); data_start = get_tx_vaddr(pctx, pctx->size - pctx->size_remaining, &tx_size); if (!data_start) { srcu_read_unlock(&einfo->ssr_sync, rcu_id); return -EINVAL; } if (!ch->streaming_ch) { if (pctx->size == pctx->size_remaining) { rc = check_write_avail(smd_write_avail, ch); if (rc <= 0) { srcu_read_unlock(&einfo->ssr_sync, rcu_id); return rc; } rc = smd_write_start(ch->smd_ch, pctx->size); if (rc) { srcu_read_unlock(&einfo->ssr_sync, rcu_id); return rc; } } rc = check_write_avail(smd_write_segment_avail, ch); if (rc <= 0) { srcu_read_unlock(&einfo->ssr_sync, rcu_id); return rc; } if (rc > tx_size) rc = tx_size; rc = smd_write_segment(ch->smd_ch, data_start, rc); if (rc < 0) { SMDXPRT_ERR(einfo, "%s: write segment failed %d\n", __func__, rc); srcu_read_unlock(&einfo->ssr_sync, rcu_id); return rc; } } else { rc = check_write_avail(smd_write_avail, ch); if (rc <= 0) { srcu_read_unlock(&einfo->ssr_sync, rcu_id); return rc; } if (rc > tx_size) rc = tx_size; rc = smd_write(ch->smd_ch, data_start, rc); if (rc < 0) { SMDXPRT_ERR(einfo, "%s: write failed %d\n", __func__, rc); srcu_read_unlock(&einfo->ssr_sync, rcu_id); return rc; } } pctx->size_remaining -= rc; if (!pctx->size_remaining) { if (!ch->streaming_ch) smd_write_end(ch->smd_ch); tx_done = kmalloc(sizeof(*tx_done), GFP_ATOMIC); tx_done->ch = ch; tx_done->iid = pctx->riid; INIT_WORK(&tx_done->work, process_tx_done); queue_work(ch->wq, &tx_done->work); } srcu_read_unlock(&einfo->ssr_sync, rcu_id); return rc; } /** * tx_cmd_rx_intent_req() - convert an rx intent request cmd to wire format and * transmit * @if_ptr: The transport to transmit on. * @lcid: The local channel id to encode. * @size: The requested intent size to encode. * * Return: 0 on success or standard Linux error code. */ static int tx_cmd_rx_intent_req(struct glink_transport_if *if_ptr, uint32_t lcid, size_t size) { struct edge_info *einfo; struct channel *ch; unsigned long flags; einfo = container_of(if_ptr, struct edge_info, xprt_if); spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) { if (lcid == ch->lcid) break; } spin_unlock_irqrestore(&einfo->channels_lock, flags); einfo->xprt_if.glink_core_if_ptr->rx_cmd_rx_intent_req_ack( &einfo->xprt_if, ch->rcid, true); einfo->xprt_if.glink_core_if_ptr->rx_cmd_remote_rx_intent_put( &einfo->xprt_if, ch->rcid, ch->next_intent_id++, size); return 0; } /** * tx_cmd_rx_intent_req_ack() - convert an rx intent request ack cmd to wire * format and transmit * @if_ptr: The transport to transmit on. * @lcid: The local channel id to encode. * @granted: The request response to encode. * * The remote side doesn't speak G-Link. The core is just acking a request we * faked. Do nothing. * * Return: 0 on success or standard Linux error code. */ static int tx_cmd_remote_rx_intent_req_ack(struct glink_transport_if *if_ptr, uint32_t lcid, bool granted) { return 0; } /** * tx_cmd_set_sigs() - convert a signal cmd to wire format and transmit * @if_ptr: The transport to transmit on. * @lcid: The local channel id to encode. * @sigs: The signals to encode. * * Return: 0 on success or standard Linux error code. */ static int tx_cmd_set_sigs(struct glink_transport_if *if_ptr, uint32_t lcid, uint32_t sigs) { struct edge_info *einfo; struct channel *ch; uint32_t set = 0; uint32_t clear = 0; unsigned long flags; einfo = container_of(if_ptr, struct edge_info, xprt_if); spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) { if (lcid == ch->lcid) break; } spin_unlock_irqrestore(&einfo->channels_lock, flags); if (sigs & SMD_DTR_SIG) set |= TIOCM_DTR; else clear |= TIOCM_DTR; if (sigs & SMD_CTS_SIG) set |= TIOCM_RTS; else clear |= TIOCM_RTS; if (sigs & SMD_CD_SIG) set |= TIOCM_CD; else clear |= TIOCM_CD; if (sigs & SMD_RI_SIG) set |= TIOCM_RI; else clear |= TIOCM_RI; return smd_tiocmset(ch->smd_ch, set, clear); } /** * poll() - poll for data on a channel * @if_ptr: The transport to transmit on. * @lcid: The local channel id for the channel. * * Return: 0 if no data available, 1 if data available, or standard Linux error * code. */ static int poll(struct glink_transport_if *if_ptr, uint32_t lcid) { struct edge_info *einfo; struct channel *ch; int rc; unsigned long flags; einfo = container_of(if_ptr, struct edge_info, xprt_if); spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) { if (lcid == ch->lcid) break; } spin_unlock_irqrestore(&einfo->channels_lock, flags); rc = smd_is_pkt_avail(ch->smd_ch); if (rc == 1) process_data_event(&ch->work); return rc; } /** * mask_rx_irq() - mask the receive irq * @if_ptr: The transport to transmit on. * @lcid: The local channel id for the channel. * @mask: True to mask the irq, false to unmask. * @pstruct: Platform defined structure for handling the masking. * * Return: 0 on success or standard Linux error code. */ static int mask_rx_irq(struct glink_transport_if *if_ptr, uint32_t lcid, bool mask, void *pstruct) { struct edge_info *einfo; struct channel *ch; int ret = 0; unsigned long flags; einfo = container_of(if_ptr, struct edge_info, xprt_if); spin_lock_irqsave(&einfo->channels_lock, flags); list_for_each_entry(ch, &einfo->channels, node) { if (lcid == ch->lcid) break; } spin_unlock_irqrestore(&einfo->channels_lock, flags); ret = smd_mask_receive_interrupt(ch->smd_ch, mask, pstruct); if (ret == 0) einfo->irq_disabled = mask; return ret; } /** * negotiate_features_v1() - determine what features of a version can be used * @if_ptr: The transport for which features are negotiated for. * @version: The version negotiated. * @features: The set of requested features. * * Return: What set of the requested features can be supported. */ static uint32_t negotiate_features_v1(struct glink_transport_if *if_ptr, const struct glink_core_version *version, uint32_t features) { return features & version->features; } /** * init_xprt_if() - initialize the xprt_if for an edge * @einfo: The edge to initialize. */ static void init_xprt_if(struct edge_info *einfo) { einfo->xprt_if.tx_cmd_version = tx_cmd_version; einfo->xprt_if.tx_cmd_version_ack = tx_cmd_version_ack; einfo->xprt_if.set_version = set_version; einfo->xprt_if.tx_cmd_ch_open = tx_cmd_ch_open; einfo->xprt_if.tx_cmd_ch_close = tx_cmd_ch_close; einfo->xprt_if.tx_cmd_ch_remote_open_ack = tx_cmd_ch_remote_open_ack; einfo->xprt_if.tx_cmd_ch_remote_close_ack = tx_cmd_ch_remote_close_ack; einfo->xprt_if.ssr = ssr; einfo->xprt_if.allocate_rx_intent = allocate_rx_intent; einfo->xprt_if.deallocate_rx_intent = deallocate_rx_intent; einfo->xprt_if.tx_cmd_local_rx_intent = tx_cmd_local_rx_intent; einfo->xprt_if.tx_cmd_local_rx_done = tx_cmd_local_rx_done; einfo->xprt_if.tx = tx; einfo->xprt_if.tx_cmd_rx_intent_req = tx_cmd_rx_intent_req; einfo->xprt_if.tx_cmd_remote_rx_intent_req_ack = tx_cmd_remote_rx_intent_req_ack; einfo->xprt_if.tx_cmd_set_sigs = tx_cmd_set_sigs; einfo->xprt_if.poll = poll; einfo->xprt_if.mask_rx_irq = mask_rx_irq; } /** * init_xprt_cfg() - initialize the xprt_cfg for an edge * @einfo: The edge to initialize. */ static void init_xprt_cfg(struct edge_info *einfo) { einfo->xprt_cfg.name = XPRT_NAME; einfo->xprt_cfg.versions = versions; einfo->xprt_cfg.versions_entries = ARRAY_SIZE(versions); einfo->xprt_cfg.max_cid = SZ_64; einfo->xprt_cfg.max_iid = SZ_128; } static struct platform_driver migration_driver = { .probe = ctl_ch_probe, .driver = { .name = "GLINK_CTRL", .owner = THIS_MODULE, }, }; static int __init glink_smd_xprt_init(void) { int i; int rc; struct edge_info *einfo; for (i = 0; i < NUM_EDGES; ++i) { einfo = &edge_infos[i]; init_xprt_cfg(einfo); init_xprt_if(einfo); INIT_LIST_HEAD(&einfo->channels); spin_lock_init(&einfo->channels_lock); init_srcu_struct(&einfo->ssr_sync); mutex_init(&einfo->smd_lock); mutex_init(&einfo->in_ssr_lock); mutex_init(&einfo->rx_cmd_lock); INIT_DELAYED_WORK(&einfo->ssr_work, ssr_work_func); INIT_WORK(&einfo->work, process_ctl_event); rc = glink_core_register_transport(&einfo->xprt_if, &einfo->xprt_cfg); if (rc) SMDXPRT_ERR(einfo, "%s: %s glink register xprt failed %d\n", __func__, einfo->xprt_cfg.edge, rc); else einfo->xprt_if.glink_core_if_ptr->link_up( &einfo->xprt_if); } platform_driver_register(&migration_driver); return 0; } arch_initcall(glink_smd_xprt_init); MODULE_DESCRIPTION("MSM G-Link SMD Transport"); MODULE_LICENSE("GPL v2");