| /* Copyright (c) 2014-2016, 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 <linux/err.h> |
| #include <linux/ipc_logging.h> |
| #include <linux/list.h> |
| #include <linux/module.h> |
| #include <linux/notifier.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| #include <linux/suspend.h> |
| #include <linux/random.h> |
| #include <soc/qcom/glink.h> |
| #include <soc/qcom/subsystem_notif.h> |
| #include <soc/qcom/subsystem_restart.h> |
| #include "glink_private.h" |
| |
| #define GLINK_SSR_REPLY_TIMEOUT HZ |
| #define GLINK_SSR_INTENT_REQ_TIMEOUT_MS 500 |
| #define GLINK_SSR_EVENT_INIT ~0 |
| #define NUM_LOG_PAGES 3 |
| |
| #define GLINK_SSR_LOG(x...) do { \ |
| if (glink_ssr_log_ctx) \ |
| ipc_log_string(glink_ssr_log_ctx, x); \ |
| } while (0) |
| |
| #define GLINK_SSR_ERR(x...) do { \ |
| pr_err(x); \ |
| GLINK_SSR_LOG(x); \ |
| } while (0) |
| |
| static void *glink_ssr_log_ctx; |
| |
| /* Global restart counter */ |
| static uint32_t sequence_number; |
| |
| /* Flag indicating if responses were received for all SSR notifications */ |
| static bool notifications_successful; |
| |
| /* Completion for setting notifications_successful */ |
| struct completion notifications_successful_complete; |
| |
| /** |
| * struct restart_notifier_block - restart notifier wrapper structure |
| * subsystem: the name of the subsystem as recognized by the SSR framework |
| * nb: notifier block structure used by the SSR framework |
| */ |
| struct restart_notifier_block { |
| const char *subsystem; |
| struct notifier_block nb; |
| }; |
| |
| /** |
| * struct configure_and_open_ch_work - Work structure for used for opening |
| * glink_ssr channels |
| * edge: The G-Link edge obtained from the link state callback |
| * transport: The G-Link transport obtained from the link state callback |
| * link_state: The link state obtained from the link state callback |
| * ss_info: Subsystem information structure containing the info for this |
| * callback |
| * work: Work structure |
| */ |
| struct configure_and_open_ch_work { |
| char edge[GLINK_NAME_SIZE]; |
| char transport[GLINK_NAME_SIZE]; |
| enum glink_link_state link_state; |
| struct subsys_info *ss_info; |
| struct work_struct work; |
| }; |
| |
| /** |
| * struct close_ch_work - Work structure for used for closing glink_ssr channels |
| * edge: The G-Link edge name for the channel being closed |
| * handle: G-Link channel handle to be closed |
| * work: Work structure |
| */ |
| struct close_ch_work { |
| char edge[GLINK_NAME_SIZE]; |
| void *handle; |
| struct work_struct work; |
| }; |
| |
| static int glink_ssr_restart_notifier_cb(struct notifier_block *this, |
| unsigned long code, |
| void *data); |
| static void delete_ss_info_notify_list(struct subsys_info *ss_info); |
| static int configure_and_open_channel(struct subsys_info *ss_info); |
| static struct workqueue_struct *glink_ssr_wq; |
| |
| static LIST_HEAD(subsystem_list); |
| static atomic_t responses_remaining = ATOMIC_INIT(0); |
| static wait_queue_head_t waitqueue; |
| |
| static void link_state_cb_worker(struct work_struct *work) |
| { |
| unsigned long flags; |
| struct configure_and_open_ch_work *ch_open_work = |
| container_of(work, struct configure_and_open_ch_work, work); |
| struct subsys_info *ss_info = ch_open_work->ss_info; |
| |
| GLINK_SSR_LOG("<SSR> %s: LINK STATE[%d] %s:%s\n", __func__, |
| ch_open_work->link_state, ch_open_work->edge, |
| ch_open_work->transport); |
| |
| if (ss_info && ch_open_work->link_state == GLINK_LINK_STATE_UP) { |
| spin_lock_irqsave(&ss_info->link_up_lock, flags); |
| if (!ss_info->link_up) { |
| ss_info->link_up = true; |
| spin_unlock_irqrestore(&ss_info->link_up_lock, flags); |
| if (!configure_and_open_channel(ss_info)) { |
| glink_unregister_link_state_cb( |
| ss_info->link_state_handle); |
| ss_info->link_state_handle = NULL; |
| } |
| kfree(ch_open_work); |
| return; |
| } |
| spin_unlock_irqrestore(&ss_info->link_up_lock, flags); |
| } else { |
| if (ss_info) { |
| spin_lock_irqsave(&ss_info->link_up_lock, flags); |
| ss_info->link_up = false; |
| spin_unlock_irqrestore(&ss_info->link_up_lock, flags); |
| ss_info->handle = NULL; |
| } else { |
| GLINK_SSR_ERR("<SSR> %s: ss_info is NULL\n", __func__); |
| } |
| } |
| |
| kfree(ch_open_work); |
| } |
| |
| /** |
| * glink_lbsrv_link_state_cb() - Callback to receive link state updates |
| * @cb_info: Information containing link & its state. |
| * @priv: Private data passed during the link state registration. |
| * |
| * This function is called by the G-Link core to notify the glink_ssr module |
| * regarding the link state updates. This function is registered with the |
| * G-Link core by the loopback server during glink_register_link_state_cb(). |
| */ |
| static void glink_ssr_link_state_cb(struct glink_link_state_cb_info *cb_info, |
| void *priv) |
| { |
| struct subsys_info *ss_info; |
| struct configure_and_open_ch_work *open_ch_work; |
| |
| if (!cb_info) { |
| GLINK_SSR_ERR("<SSR> %s: Missing cb_data\n", __func__); |
| return; |
| } |
| |
| ss_info = get_info_for_edge(cb_info->edge); |
| |
| open_ch_work = kmalloc(sizeof(*open_ch_work), GFP_KERNEL); |
| if (!open_ch_work) { |
| GLINK_SSR_ERR("<SSR> %s: Could not allocate open_ch_work\n", |
| __func__); |
| return; |
| } |
| |
| strlcpy(open_ch_work->edge, cb_info->edge, GLINK_NAME_SIZE); |
| strlcpy(open_ch_work->transport, cb_info->transport, GLINK_NAME_SIZE); |
| open_ch_work->link_state = cb_info->link_state; |
| open_ch_work->ss_info = ss_info; |
| |
| INIT_WORK(&open_ch_work->work, link_state_cb_worker); |
| queue_work(glink_ssr_wq, &open_ch_work->work); |
| } |
| |
| /** |
| * glink_ssr_notify_rx() - RX Notification callback |
| * @handle: G-Link channel handle |
| * @priv: Private callback data |
| * @pkt_priv: Private packet data |
| * @ptr: Pointer to the data received |
| * @size: Size of the data received |
| * |
| * This function is a notification callback from the G-Link core that data |
| * has been received from the remote side. This data is validate to make |
| * sure it is a cleanup_done message and is processed accordingly if it is. |
| */ |
| void glink_ssr_notify_rx(void *handle, const void *priv, const void *pkt_priv, |
| const void *ptr, size_t size) |
| { |
| struct ssr_notify_data *cb_data = (struct ssr_notify_data *)priv; |
| struct cleanup_done_msg *resp = (struct cleanup_done_msg *)ptr; |
| |
| if (unlikely(!cb_data)) |
| goto missing_cb_data; |
| if (unlikely(!cb_data->do_cleanup_data)) |
| goto missing_do_cleanup_data; |
| if (unlikely(!resp)) |
| goto missing_response; |
| if (unlikely(resp->version != cb_data->do_cleanup_data->version)) |
| goto version_mismatch; |
| if (unlikely(resp->seq_num != cb_data->do_cleanup_data->seq_num)) |
| goto invalid_seq_number; |
| if (unlikely(resp->response != GLINK_SSR_CLEANUP_DONE)) |
| goto wrong_response; |
| |
| cb_data->responded = true; |
| atomic_dec(&responses_remaining); |
| |
| GLINK_SSR_LOG( |
| "<SSR> %s: Response from %s resp[%d] version[%d] seq_num[%d] restarted[%s]\n", |
| __func__, cb_data->edge, resp->response, |
| resp->version, resp->seq_num, |
| cb_data->do_cleanup_data->name); |
| |
| kfree(cb_data->do_cleanup_data); |
| cb_data->do_cleanup_data = NULL; |
| wake_up(&waitqueue); |
| return; |
| |
| missing_cb_data: |
| panic("%s: Missing cb_data!\n", __func__); |
| return; |
| missing_do_cleanup_data: |
| panic("%s: Missing do_cleanup data!\n", __func__); |
| return; |
| missing_response: |
| GLINK_SSR_ERR("<SSR> %s: Missing response data\n", __func__); |
| return; |
| version_mismatch: |
| GLINK_SSR_ERR("<SSR> %s: Version mismatch. %s[%d], %s[%d]\n", __func__, |
| "do_cleanup version", cb_data->do_cleanup_data->version, |
| "cleanup_done version", resp->version); |
| return; |
| invalid_seq_number: |
| GLINK_SSR_ERR("<SSR> %s: Invalid seq. number. %s[%d], %s[%d]\n", |
| __func__, "do_cleanup seq num", |
| cb_data->do_cleanup_data->seq_num, |
| "cleanup_done seq_num", resp->seq_num); |
| return; |
| wrong_response: |
| GLINK_SSR_ERR("<SSR> %s: Not a cleaup_done message. %s[%d]\n", __func__, |
| "cleanup_done response", resp->response); |
| } |
| |
| /** |
| * glink_ssr_notify_tx_done() - Transmit finished notification callback |
| * @handle: G-Link channel handle |
| * @priv: Private callback data |
| * @pkt_priv: Private packet data |
| * @ptr: Pointer to the data received |
| * |
| * This function is a notification callback from the G-Link core that data |
| * we sent has finished transmitting. |
| */ |
| void glink_ssr_notify_tx_done(void *handle, const void *priv, |
| const void *pkt_priv, const void *ptr) |
| { |
| struct ssr_notify_data *cb_data = (struct ssr_notify_data *)priv; |
| |
| if (unlikely(!cb_data)) { |
| panic("%s: cb_data is NULL!\n", __func__); |
| return; |
| } |
| |
| GLINK_SSR_LOG("<SSR> %s: Notified %s of restart\n", |
| __func__, cb_data->edge); |
| |
| cb_data->tx_done = true; |
| } |
| |
| void close_ch_worker(struct work_struct *work) |
| { |
| unsigned long flags; |
| void *link_state_handle; |
| struct subsys_info *ss_info; |
| struct close_ch_work *close_work = |
| container_of(work, struct close_ch_work, work); |
| |
| glink_close(close_work->handle); |
| |
| ss_info = get_info_for_edge(close_work->edge); |
| if (WARN_ON(!ss_info)) |
| return; |
| |
| spin_lock_irqsave(&ss_info->link_up_lock, flags); |
| ss_info->link_up = false; |
| spin_unlock_irqrestore(&ss_info->link_up_lock, flags); |
| |
| if (WARN_ON(ss_info->link_state_handle != NULL)) |
| return; |
| link_state_handle = glink_register_link_state_cb(ss_info->link_info, |
| NULL); |
| |
| if (IS_ERR_OR_NULL(link_state_handle)) |
| GLINK_SSR_ERR("<SSR> %s: %s, ret[%d]\n", __func__, |
| "Couldn't register link state cb", |
| (int)PTR_ERR(link_state_handle)); |
| else |
| ss_info->link_state_handle = link_state_handle; |
| |
| if (WARN_ON(!ss_info->cb_data)) |
| return; |
| kfree(ss_info->cb_data); |
| kfree(close_work); |
| } |
| |
| /** |
| * glink_ssr_notify_state() - Channel state notification callback |
| * @handle: G-Link channel handle |
| * @priv: Private callback data |
| * @event: The state that has been transitioned to |
| * |
| * This function is a notification callback from the G-Link core that the |
| * channel state has changed. |
| */ |
| void glink_ssr_notify_state(void *handle, const void *priv, unsigned int event) |
| { |
| struct ssr_notify_data *cb_data = (struct ssr_notify_data *)priv; |
| struct close_ch_work *close_work; |
| |
| if (!cb_data) { |
| GLINK_SSR_ERR("<SSR> %s: Could not allocate data for cb_data\n", |
| __func__); |
| } else { |
| GLINK_SSR_LOG("<SSR> %s: event[%d]\n", |
| __func__, event); |
| cb_data->event = event; |
| if (event == GLINK_REMOTE_DISCONNECTED) { |
| close_work = |
| kmalloc(sizeof(struct close_ch_work), |
| GFP_KERNEL); |
| if (!close_work) { |
| GLINK_SSR_ERR( |
| "<SSR> %s: Could not allocate %s\n", |
| __func__, "close work"); |
| return; |
| } |
| |
| strlcpy(close_work->edge, cb_data->edge, |
| sizeof(close_work->edge)); |
| close_work->handle = handle; |
| INIT_WORK(&close_work->work, close_ch_worker); |
| queue_work(glink_ssr_wq, &close_work->work); |
| } |
| } |
| } |
| |
| /** |
| * glink_ssr_notify_rx_intent_req() - RX intent request notification callback |
| * @handle: G-Link channel handle |
| * @priv: Private callback data |
| * @req_size: The size of the requested intent |
| * |
| * This function is a notification callback from the G-Link core of the remote |
| * side's request for an RX intent to be queued. |
| * |
| * Return: Boolean indicating whether or not the request was successfully |
| * received |
| */ |
| bool glink_ssr_notify_rx_intent_req(void *handle, const void *priv, |
| size_t req_size) |
| { |
| struct ssr_notify_data *cb_data = (struct ssr_notify_data *)priv; |
| |
| if (!cb_data) { |
| GLINK_SSR_ERR("<SSR> %s: Could not allocate data for cb_data\n", |
| __func__); |
| return false; |
| } |
| GLINK_SSR_LOG("<SSR> %s: rx_intent_req of size %zu\n", |
| __func__, req_size); |
| return true; |
| } |
| |
| /** |
| * glink_ssr_restart_notifier_cb() - SSR restart notifier callback function |
| * @this: Notifier block used by the SSR framework |
| * @code: The SSR code for which stage of restart is occurring |
| * @data: Structure containing private data - not used here. |
| * |
| * This function is a callback for the SSR framework. From here we initiate |
| * our handling of SSR. |
| * |
| * Return: Status of SSR handling |
| */ |
| static int glink_ssr_restart_notifier_cb(struct notifier_block *this, |
| unsigned long code, |
| void *data) |
| { |
| int ret = 0; |
| struct subsys_info *ss_info = NULL; |
| struct restart_notifier_block *notifier = |
| container_of(this, struct restart_notifier_block, nb); |
| |
| if (code == SUBSYS_AFTER_SHUTDOWN) { |
| GLINK_SSR_LOG("<SSR> %s: %s: subsystem restart for %s\n", |
| __func__, "SUBSYS_AFTER_SHUTDOWN", |
| notifier->subsystem); |
| ss_info = get_info_for_subsystem(notifier->subsystem); |
| if (ss_info == NULL) { |
| GLINK_SSR_ERR("<SSR> %s: ss_info is NULL\n", __func__); |
| return -EINVAL; |
| } |
| |
| glink_ssr(ss_info->edge); |
| ret = notify_for_subsystem(ss_info); |
| |
| if (ret) { |
| GLINK_SSR_ERR("<SSR>: %s: %s, ret[%d]\n", __func__, |
| "Subsystem notification failed", ret); |
| return ret; |
| } |
| } |
| return NOTIFY_DONE; |
| } |
| |
| /** |
| * notify for subsystem() - Notify other subsystems that a subsystem is being |
| * restarted |
| * @ss_info: Subsystem info structure for the subsystem being restarted |
| * |
| * This function sends notifications to affected subsystems that the subsystem |
| * in ss_info is being restarted, and waits for the cleanup done response from |
| * all of those subsystems. It also initiates any local cleanup that is |
| * necessary. |
| * |
| * Return: 0 on success, standard error codes otherwise |
| */ |
| int notify_for_subsystem(struct subsys_info *ss_info) |
| { |
| struct subsys_info *ss_info_channel; |
| struct subsys_info_leaf *ss_leaf_entry; |
| struct do_cleanup_msg *do_cleanup_data; |
| void *handle; |
| int wait_ret; |
| int ret; |
| unsigned long flags; |
| |
| if (!ss_info) { |
| GLINK_SSR_ERR("<SSR> %s: ss_info structure invalid\n", |
| __func__); |
| return -EINVAL; |
| } |
| |
| /* |
| * No locking is needed here because ss_info->notify_list_len is |
| * only modified during setup. |
| */ |
| atomic_set(&responses_remaining, ss_info->notify_list_len); |
| init_waitqueue_head(&waitqueue); |
| notifications_successful = true; |
| |
| list_for_each_entry(ss_leaf_entry, &ss_info->notify_list, |
| notify_list_node) { |
| GLINK_SSR_LOG( |
| "<SSR> %s: Notifying: %s:%s of %s restart, seq_num[%d]\n", |
| __func__, ss_leaf_entry->edge, |
| ss_leaf_entry->xprt, ss_info->edge, |
| sequence_number); |
| |
| ss_info_channel = |
| get_info_for_subsystem(ss_leaf_entry->ssr_name); |
| if (ss_info_channel == NULL) { |
| GLINK_SSR_ERR( |
| "<SSR> %s: unable to find subsystem name\n", |
| __func__); |
| return -ENODEV; |
| } |
| handle = ss_info_channel->handle; |
| ss_leaf_entry->cb_data = ss_info_channel->cb_data; |
| |
| spin_lock_irqsave(&ss_info->link_up_lock, flags); |
| if (IS_ERR_OR_NULL(ss_info_channel->handle) || |
| !ss_info_channel->cb_data || |
| !ss_info_channel->link_up || |
| ss_info_channel->cb_data->event |
| != GLINK_CONNECTED) { |
| |
| GLINK_SSR_LOG( |
| "<SSR> %s: %s:%s %s[%d], %s[%p], %s[%d]\n", |
| __func__, ss_leaf_entry->edge, "Not connected", |
| "resp. remaining", |
| atomic_read(&responses_remaining), "handle", |
| ss_info_channel->handle, "link_up", |
| ss_info_channel->link_up); |
| |
| spin_unlock_irqrestore(&ss_info->link_up_lock, flags); |
| atomic_dec(&responses_remaining); |
| continue; |
| } |
| spin_unlock_irqrestore(&ss_info->link_up_lock, flags); |
| |
| do_cleanup_data = kmalloc(sizeof(struct do_cleanup_msg), |
| GFP_KERNEL); |
| if (!do_cleanup_data) { |
| GLINK_SSR_ERR( |
| "%s %s: Could not allocate do_cleanup_msg\n", |
| "<SSR>", __func__); |
| return -ENOMEM; |
| } |
| |
| do_cleanup_data->version = 0; |
| do_cleanup_data->command = GLINK_SSR_DO_CLEANUP; |
| do_cleanup_data->seq_num = sequence_number; |
| do_cleanup_data->name_len = strlen(ss_info->edge); |
| strlcpy(do_cleanup_data->name, ss_info->edge, |
| do_cleanup_data->name_len + 1); |
| ss_leaf_entry->cb_data->do_cleanup_data = do_cleanup_data; |
| |
| ret = glink_queue_rx_intent(handle, |
| (void *)ss_leaf_entry->cb_data, |
| sizeof(struct cleanup_done_msg)); |
| if (ret) { |
| GLINK_SSR_ERR( |
| "%s %s: %s, ret[%d], resp. remaining[%d]\n", |
| "<SSR>", __func__, |
| "queue_rx_intent failed", ret, |
| atomic_read(&responses_remaining)); |
| kfree(do_cleanup_data); |
| ss_leaf_entry->cb_data->do_cleanup_data = NULL; |
| |
| if (strcmp(ss_leaf_entry->ssr_name, "rpm")) { |
| subsystem_restart(ss_leaf_entry->ssr_name); |
| ss_leaf_entry->restarted = true; |
| } else { |
| panic("%s: Could not queue intent for RPM!\n", |
| __func__); |
| } |
| atomic_dec(&responses_remaining); |
| continue; |
| } |
| |
| if (strcmp(ss_leaf_entry->ssr_name, "rpm")) |
| ret = glink_tx(handle, ss_leaf_entry->cb_data, |
| do_cleanup_data, |
| sizeof(*do_cleanup_data), |
| GLINK_TX_REQ_INTENT); |
| else |
| ret = glink_tx(handle, ss_leaf_entry->cb_data, |
| do_cleanup_data, |
| sizeof(*do_cleanup_data), |
| GLINK_TX_SINGLE_THREADED); |
| |
| if (ret) { |
| GLINK_SSR_ERR("<SSR> %s: tx failed, ret[%d], %s[%d]\n", |
| __func__, ret, "resp. remaining", |
| atomic_read(&responses_remaining)); |
| kfree(do_cleanup_data); |
| ss_leaf_entry->cb_data->do_cleanup_data = NULL; |
| |
| if (strcmp(ss_leaf_entry->ssr_name, "rpm")) { |
| subsystem_restart(ss_leaf_entry->ssr_name); |
| ss_leaf_entry->restarted = true; |
| } else { |
| panic("%s: glink_tx() to RPM failed!\n", |
| __func__); |
| } |
| atomic_dec(&responses_remaining); |
| continue; |
| } |
| |
| sequence_number++; |
| } |
| |
| wait_ret = wait_event_timeout(waitqueue, |
| atomic_read(&responses_remaining) == 0, |
| GLINK_SSR_REPLY_TIMEOUT); |
| |
| list_for_each_entry(ss_leaf_entry, &ss_info->notify_list, |
| notify_list_node) { |
| if (!wait_ret && !IS_ERR_OR_NULL(ss_leaf_entry->cb_data) |
| && !ss_leaf_entry->cb_data->responded) { |
| GLINK_SSR_ERR("%s %s: Subsystem %s %s\n", |
| "<SSR>", __func__, ss_leaf_entry->edge, |
| "failed to respond. Restarting."); |
| |
| notifications_successful = false; |
| |
| /* Check for RPM, as it can't be restarted */ |
| if (!strcmp(ss_leaf_entry->ssr_name, "rpm")) |
| panic("%s: RPM failed to respond!\n", __func__); |
| else if (!ss_leaf_entry->restarted) |
| subsystem_restart(ss_leaf_entry->ssr_name); |
| } |
| ss_leaf_entry->restarted = false; |
| |
| if (!IS_ERR_OR_NULL(ss_leaf_entry->cb_data)) |
| ss_leaf_entry->cb_data->responded = false; |
| } |
| complete(¬ifications_successful_complete); |
| return 0; |
| } |
| EXPORT_SYMBOL(notify_for_subsystem); |
| |
| /** |
| * configure_and_open_channel() - configure and open a G-Link channel for |
| * the given subsystem |
| * @ss_info: The subsys_info structure where the channel will be stored |
| * |
| * Return: 0 on success, standard error codes otherwise |
| */ |
| static int configure_and_open_channel(struct subsys_info *ss_info) |
| { |
| struct glink_open_config open_cfg; |
| struct ssr_notify_data *cb_data = NULL; |
| void *handle = NULL; |
| |
| if (!ss_info) { |
| GLINK_SSR_ERR("<SSR> %s: ss_info structure invalid\n", |
| __func__); |
| return -EINVAL; |
| } |
| |
| cb_data = kmalloc(sizeof(struct ssr_notify_data), GFP_KERNEL); |
| if (!cb_data) { |
| GLINK_SSR_ERR("<SSR> %s: Could not allocate cb_data\n", |
| __func__); |
| return -ENOMEM; |
| } |
| cb_data->responded = false; |
| cb_data->event = GLINK_SSR_EVENT_INIT; |
| cb_data->edge = ss_info->edge; |
| ss_info->cb_data = cb_data; |
| |
| memset(&open_cfg, 0, sizeof(struct glink_open_config)); |
| |
| if (ss_info->xprt) { |
| open_cfg.transport = ss_info->xprt; |
| } else { |
| open_cfg.transport = NULL; |
| open_cfg.options = GLINK_OPT_INITIAL_XPORT; |
| } |
| open_cfg.edge = ss_info->edge; |
| open_cfg.name = "glink_ssr"; |
| open_cfg.notify_rx = glink_ssr_notify_rx; |
| open_cfg.notify_tx_done = glink_ssr_notify_tx_done; |
| open_cfg.notify_state = glink_ssr_notify_state; |
| open_cfg.notify_rx_intent_req = glink_ssr_notify_rx_intent_req; |
| open_cfg.priv = ss_info->cb_data; |
| open_cfg.rx_intent_req_timeout_ms = GLINK_SSR_INTENT_REQ_TIMEOUT_MS; |
| |
| handle = glink_open(&open_cfg); |
| if (IS_ERR_OR_NULL(handle)) { |
| GLINK_SSR_ERR( |
| "<SSR> %s:%s %s: unable to open channel, ret[%d]\n", |
| open_cfg.edge, open_cfg.name, __func__, |
| (int)PTR_ERR(handle)); |
| kfree(cb_data); |
| cb_data = NULL; |
| ss_info->cb_data = NULL; |
| return PTR_ERR(handle); |
| } |
| ss_info->handle = handle; |
| return 0; |
| } |
| |
| /** |
| * get_info_for_subsystem() - Retrieve information about a subsystem from the |
| * global subsystem_info_list |
| * @subsystem: The name of the subsystem recognized by the SSR |
| * framework |
| * |
| * Return: subsys_info structure containing info for the requested subsystem; |
| * NULL if no structure can be found for the requested subsystem |
| */ |
| struct subsys_info *get_info_for_subsystem(const char *subsystem) |
| { |
| struct subsys_info *ss_info_entry; |
| |
| list_for_each_entry(ss_info_entry, &subsystem_list, |
| subsystem_list_node) { |
| if (!strcmp(subsystem, ss_info_entry->ssr_name)) |
| return ss_info_entry; |
| } |
| |
| return NULL; |
| } |
| EXPORT_SYMBOL(get_info_for_subsystem); |
| |
| /** |
| * get_info_for_edge() - Retrieve information about a subsystem from the |
| * global subsystem_info_list |
| * @edge: The name of the edge recognized by G-Link |
| * |
| * Return: subsys_info structure containing info for the requested subsystem; |
| * NULL if no structure can be found for the requested subsystem |
| */ |
| struct subsys_info *get_info_for_edge(const char *edge) |
| { |
| struct subsys_info *ss_info_entry; |
| |
| list_for_each_entry(ss_info_entry, &subsystem_list, |
| subsystem_list_node) { |
| if (!strcmp(edge, ss_info_entry->edge)) |
| return ss_info_entry; |
| } |
| |
| return NULL; |
| } |
| EXPORT_SYMBOL(get_info_for_edge); |
| |
| /** |
| * glink_ssr_get_seq_num() - Get the current SSR sequence number |
| * |
| * Return: The current SSR sequence number |
| */ |
| uint32_t glink_ssr_get_seq_num(void) |
| { |
| return sequence_number; |
| } |
| EXPORT_SYMBOL(glink_ssr_get_seq_num); |
| |
| /** |
| * delete_ss_info_notify_list() - Delete the notify list for a subsystem |
| * @ss_info: The subsystem info structure |
| */ |
| static void delete_ss_info_notify_list(struct subsys_info *ss_info) |
| { |
| struct subsys_info_leaf *leaf, *temp; |
| |
| list_for_each_entry_safe(leaf, temp, &ss_info->notify_list, |
| notify_list_node) { |
| list_del(&leaf->notify_list_node); |
| kfree(leaf); |
| } |
| } |
| |
| /** |
| * glink_ssr_wait_cleanup_done() - Get the value of the |
| * notifications_successful flag. |
| * @timeout_multiplier: timeout multiplier for waiting on all processors |
| * |
| * Return: True if cleanup_done received from all processors, false otherwise |
| */ |
| bool glink_ssr_wait_cleanup_done(unsigned int ssr_timeout_multiplier) |
| { |
| int wait_ret = |
| wait_for_completion_timeout(¬ifications_successful_complete, |
| ssr_timeout_multiplier * GLINK_SSR_REPLY_TIMEOUT); |
| reinit_completion(¬ifications_successful_complete); |
| |
| if (!notifications_successful || !wait_ret) |
| return false; |
| else |
| return true; |
| } |
| EXPORT_SYMBOL(glink_ssr_wait_cleanup_done); |
| |
| /** |
| * glink_ssr_probe() - G-Link SSR platform device probe function |
| * @pdev: Pointer to the platform device structure |
| * |
| * This function parses DT for information on which subsystems should be |
| * notified when each subsystem undergoes SSR. The global subsystem information |
| * list is built from this information. In addition, SSR notifier callback |
| * functions are registered here for the necessary subsystems. |
| * |
| * Return: 0 on success, standard error codes otherwise |
| */ |
| static int glink_ssr_probe(struct platform_device *pdev) |
| { |
| struct device_node *node; |
| struct device_node *phandle_node; |
| struct restart_notifier_block *nb; |
| struct subsys_info *ss_info; |
| struct subsys_info_leaf *ss_info_leaf; |
| struct glink_link_info *link_info; |
| char *key; |
| const char *edge; |
| const char *subsys_name; |
| const char *xprt; |
| void *handle; |
| void *link_state_handle; |
| int phandle_index = 0; |
| int ret = 0; |
| |
| if (!pdev) { |
| GLINK_SSR_ERR("<SSR> %s: pdev is NULL\n", __func__); |
| ret = -EINVAL; |
| goto pdev_null_or_ss_info_alloc_failed; |
| } |
| |
| node = pdev->dev.of_node; |
| |
| ss_info = kmalloc(sizeof(*ss_info), GFP_KERNEL); |
| if (!ss_info) { |
| GLINK_SSR_ERR("<SSR> %s: %s\n", __func__, |
| "Could not allocate subsystem info structure\n"); |
| ret = -ENOMEM; |
| goto pdev_null_or_ss_info_alloc_failed; |
| } |
| INIT_LIST_HEAD(&ss_info->notify_list); |
| |
| link_info = kmalloc(sizeof(struct glink_link_info), |
| GFP_KERNEL); |
| if (!link_info) { |
| GLINK_SSR_ERR("<SSR> %s: %s\n", __func__, |
| "Could not allocate link info structure\n"); |
| ret = -ENOMEM; |
| goto link_info_alloc_failed; |
| } |
| ss_info->link_info = link_info; |
| |
| key = "label"; |
| subsys_name = of_get_property(node, key, NULL); |
| if (!subsys_name) { |
| GLINK_SSR_ERR("<SSR> %s: missing key %s\n", __func__, key); |
| ret = -ENODEV; |
| goto label_or_edge_missing; |
| } |
| |
| key = "qcom,edge"; |
| edge = of_get_property(node, key, NULL); |
| if (!edge) { |
| GLINK_SSR_ERR("<SSR> %s: missing key %s\n", __func__, key); |
| ret = -ENODEV; |
| goto label_or_edge_missing; |
| } |
| |
| key = "qcom,xprt"; |
| xprt = of_get_property(node, key, NULL); |
| if (!xprt) |
| GLINK_SSR_LOG( |
| "%s %s: no transport present for subys/edge %s/%s\n", |
| "<SSR>", __func__, subsys_name, edge); |
| |
| ss_info->ssr_name = subsys_name; |
| ss_info->edge = edge; |
| ss_info->xprt = xprt; |
| ss_info->notify_list_len = 0; |
| ss_info->link_info->transport = xprt; |
| ss_info->link_info->edge = edge; |
| ss_info->link_info->glink_link_state_notif_cb = glink_ssr_link_state_cb; |
| ss_info->link_up = false; |
| ss_info->handle = NULL; |
| ss_info->link_state_handle = NULL; |
| ss_info->cb_data = NULL; |
| spin_lock_init(&ss_info->link_up_lock); |
| |
| nb = kmalloc(sizeof(struct restart_notifier_block), GFP_KERNEL); |
| if (!nb) { |
| GLINK_SSR_ERR("<SSR> %s: Could not allocate notifier block\n", |
| __func__); |
| ret = -ENOMEM; |
| goto label_or_edge_missing; |
| } |
| |
| nb->subsystem = subsys_name; |
| nb->nb.notifier_call = glink_ssr_restart_notifier_cb; |
| |
| handle = subsys_notif_register_notifier(nb->subsystem, &nb->nb); |
| if (IS_ERR_OR_NULL(handle)) { |
| GLINK_SSR_ERR("<SSR> %s: Could not register SSR notifier cb\n", |
| __func__); |
| ret = -EINVAL; |
| goto nb_registration_fail; |
| } |
| |
| key = "qcom,notify-edges"; |
| while (true) { |
| phandle_node = of_parse_phandle(node, key, phandle_index++); |
| if (!phandle_node && phandle_index == 0) { |
| GLINK_SSR_ERR( |
| "<SSR> %s: qcom,notify-edges is not present", |
| __func__); |
| ret = -ENODEV; |
| goto notify_edges_not_present; |
| } |
| |
| if (!phandle_node) |
| break; |
| |
| ss_info_leaf = kmalloc(sizeof(struct subsys_info_leaf), |
| GFP_KERNEL); |
| if (!ss_info_leaf) { |
| GLINK_SSR_ERR( |
| "<SSR> %s: Could not allocate subsys_info_leaf\n", |
| __func__); |
| ret = -ENOMEM; |
| goto notify_edges_not_present; |
| } |
| |
| subsys_name = of_get_property(phandle_node, "label", NULL); |
| edge = of_get_property(phandle_node, "qcom,edge", NULL); |
| xprt = of_get_property(phandle_node, "qcom,xprt", NULL); |
| |
| of_node_put(phandle_node); |
| |
| if (!subsys_name || !edge) { |
| GLINK_SSR_ERR( |
| "%s, %s: Found DT node with invalid data!\n", |
| "<SSR>", __func__); |
| ret = -EINVAL; |
| goto invalid_dt_node; |
| } |
| |
| ss_info_leaf->ssr_name = subsys_name; |
| ss_info_leaf->edge = edge; |
| ss_info_leaf->xprt = xprt; |
| ss_info_leaf->restarted = false; |
| list_add_tail(&ss_info_leaf->notify_list_node, |
| &ss_info->notify_list); |
| ss_info->notify_list_len++; |
| } |
| |
| list_add_tail(&ss_info->subsystem_list_node, &subsystem_list); |
| |
| link_state_handle = glink_register_link_state_cb(ss_info->link_info, |
| NULL); |
| if (IS_ERR_OR_NULL(link_state_handle)) { |
| GLINK_SSR_ERR("<SSR> %s: Could not register link state cb\n", |
| __func__); |
| ret = PTR_ERR(link_state_handle); |
| goto link_state_register_fail; |
| } |
| ss_info->link_state_handle = link_state_handle; |
| |
| return 0; |
| |
| link_state_register_fail: |
| list_del(&ss_info->subsystem_list_node); |
| invalid_dt_node: |
| kfree(ss_info_leaf); |
| notify_edges_not_present: |
| subsys_notif_unregister_notifier(handle, &nb->nb); |
| delete_ss_info_notify_list(ss_info); |
| nb_registration_fail: |
| kfree(nb); |
| label_or_edge_missing: |
| kfree(link_info); |
| link_info_alloc_failed: |
| kfree(ss_info); |
| pdev_null_or_ss_info_alloc_failed: |
| return ret; |
| } |
| |
| static const struct of_device_id match_table[] = { |
| { .compatible = "qcom,glink_ssr" }, |
| {}, |
| }; |
| |
| static struct platform_driver glink_ssr_driver = { |
| .probe = glink_ssr_probe, |
| .driver = { |
| .name = "msm_glink_ssr", |
| .owner = THIS_MODULE, |
| .of_match_table = match_table, |
| }, |
| }; |
| |
| static int glink_ssr_init(void) |
| { |
| int ret; |
| |
| glink_ssr_log_ctx = |
| ipc_log_context_create(NUM_LOG_PAGES, "glink_ssr", 0); |
| glink_ssr_wq = create_singlethread_workqueue("glink_ssr_wq"); |
| ret = platform_driver_register(&glink_ssr_driver); |
| if (ret) |
| GLINK_SSR_ERR("<SSR> %s: %s ret: %d\n", __func__, |
| "glink_ssr driver registration failed", ret); |
| |
| notifications_successful = false; |
| init_completion(¬ifications_successful_complete); |
| return 0; |
| } |
| |
| module_init(glink_ssr_init); |
| |
| MODULE_DESCRIPTION("MSM Generic Link (G-Link) SSR Module"); |
| MODULE_LICENSE("GPL v2"); |