/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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 rx_done_ch_work - Work structure used for sending rx_done on * glink_ssr channels * handle: G-Link channel handle to be used for sending rx_done * ptr: Intent pointer data provided in notify rx function * work: Work structure */ struct rx_done_ch_work { void *handle; const void *ptr; 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 rx_done_cb_worker(struct work_struct *work) { struct rx_done_ch_work *rx_done_work = container_of(work, struct rx_done_ch_work, work); glink_rx_done(rx_done_work->handle, rx_done_work->ptr, false); kfree(rx_done_work); } 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(" %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(" %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(" %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(" %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; struct rx_done_ch_work *rx_done_work; rx_done_work = kmalloc(sizeof(*rx_done_work), GFP_ATOMIC); if (!rx_done_work) { GLINK_SSR_ERR(" %s: Could not allocate rx_done_work\n", __func__); return; } 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( " %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; rx_done_work->ptr = ptr; rx_done_work->handle = handle; INIT_WORK(&rx_done_work->work, rx_done_cb_worker); queue_work(glink_ssr_wq, &rx_done_work->work); 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(" %s: Missing response data\n", __func__); return; version_mismatch: GLINK_SSR_ERR(" %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(" %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(" %s: Not a cleaup_done message. %s[%d]\n", __func__, "cleanup_done response", resp->response); return; } /** * 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(" %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); BUG_ON(!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); BUG_ON(ss_info->link_state_handle != NULL); link_state_handle = glink_register_link_state_cb(ss_info->link_info, NULL); if (IS_ERR_OR_NULL(link_state_handle)) GLINK_SSR_ERR(" %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; BUG_ON(!ss_info->cb_data); 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 event) { struct ssr_notify_data *cb_data = (struct ssr_notify_data *)priv; struct close_ch_work *close_work; if (!cb_data) { GLINK_SSR_ERR(" %s: Could not allocate data for cb_data\n", __func__); } else { GLINK_SSR_LOG(" %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( " %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(" %s: Could not allocate data for cb_data\n", __func__); return false; } else { GLINK_SSR_LOG(" %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(" %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(" %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(": %s: %s, ret[%d]\n", __func__, "Subsystem notification failed", ret); return ret; } pr_emerg(": glink SUBSYS_AFTER_SHUTDOWN cleanup done\n"); } else if (code == SUBSYS_BEFORE_POWERUP) { GLINK_SSR_LOG(" %s: %s: subsystem restart for %s\n", __func__, "SUBSYS_BEFORE_POWERUP", notifier->subsystem); ss_info = get_info_for_subsystem(notifier->subsystem); if (ss_info == NULL) { GLINK_SSR_ERR(" %s: ss_info is NULL\n", __func__); return -EINVAL; } glink_reinit_ssr(ss_info->edge); pr_emerg(": glink SUBSYS_BEFOER_POWERUP reinit done\n"); } 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(" %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( " %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( " %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( " %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", "", __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", "", __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(" %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", "", __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(" %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(" %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( " %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 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 = NULL; 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; int noedge_notification = 0; if (!pdev) { GLINK_SSR_ERR(" %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(" %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(" %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(" %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(" %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", "", __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(" %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(" %s: Could not register SSR notifier cb\n", __func__); ret = -EINVAL; goto nb_registration_fail; } noedge_notification = of_property_read_bool(pdev->dev.of_node, "qca,no-notify-edges"); key = "qcom,notify-edges"; while (true) { if (noedge_notification) break; phandle_node = of_parse_phandle(node, key, phandle_index++); if (!phandle_node && phandle_index == 0) { GLINK_SSR_ERR( " %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( " %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", "", __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(" %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 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(" %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");