text: /* Copyright (c) 2011, 2013, 2016-2017, 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.
   *
   *
   * Subsystem Notifier -- Provides notifications
   * of subsys events.
   *
   * Use subsys_notif_register_notifier to register for notifications
   * and subsys_notif_queue_notification to send notifications.
   *
   */
  
  #include <linux/notifier.h>
  #include <linux/init.h>
  #include <linux/debugfs.h>
  #include <linux/module.h>
  #include <linux/workqueue.h>
  #include <linux/stringify.h>
  #include <linux/delay.h>
  #include <linux/slab.h>
  #include <soc/qcom/subsystem_notif.h>
  
  
  struct subsys_notif_info {
  	char name[50];
  	struct srcu_notifier_head subsys_notif_rcvr_list;
  	struct list_head list;
  };
  
  static LIST_HEAD(subsystem_list);
  static DEFINE_MUTEX(notif_lock);
  static DEFINE_MUTEX(notif_add_lock);
  
  #if defined(SUBSYS_RESTART_DEBUG)
  static void subsys_notif_reg_test_notifier(const char *);
  #endif
  
  static struct subsys_notif_info *_notif_find_subsys(const char *subsys_name)
  {
  	struct subsys_notif_info *subsys;
  
  	mutex_lock(&notif_lock);
  	list_for_each_entry(subsys, &subsystem_list, list)
  		if (!strcmp(subsys->name, subsys_name)) {
  			mutex_unlock(&notif_lock);
  			return subsys;
  		}
  	mutex_unlock(&notif_lock);
  
  	return NULL;
  }
  
  void *subsys_notif_register_notifier(
  			const char *subsys_name, struct notifier_block *nb)
  {
  	int ret;
  	struct subsys_notif_info *subsys = _notif_find_subsys(subsys_name);
  
  	if (!subsys) {
  
  		/* Possible first time reference to this subsystem. Add it. */
  		subsys = (struct subsys_notif_info *)
  				subsys_notif_add_subsys(subsys_name);
  
  		if (!subsys)
  			return ERR_PTR(-EINVAL);
  	}
  
  	ret = srcu_notifier_chain_register(
  		&subsys->subsys_notif_rcvr_list, nb);
  
  	if (ret < 0)
  		return ERR_PTR(ret);
  
  	return subsys;
  }
  EXPORT_SYMBOL(subsys_notif_register_notifier);
  
  int subsys_notif_unregister_notifier(void *subsys_handle,
  				struct notifier_block *nb)
  {
  	int ret;
  	struct subsys_notif_info *subsys =
  			(struct subsys_notif_info *)subsys_handle;
  
  	if (!subsys)
  		return -EINVAL;
  
  	ret = srcu_notifier_chain_unregister(
  		&subsys->subsys_notif_rcvr_list, nb);
  
  	return ret;
  }
  EXPORT_SYMBOL(subsys_notif_unregister_notifier);
  
  void *subsys_notif_add_subsys(const char *subsys_name)
  {
  	struct subsys_notif_info *subsys = NULL;
  
  	if (!subsys_name)
  		goto done;
  
  	mutex_lock(&notif_add_lock);
  
  	subsys = _notif_find_subsys(subsys_name);
  
  	if (subsys) {
  		mutex_unlock(&notif_add_lock);
  		goto done;
  	}
  
  	subsys = kmalloc(sizeof(struct subsys_notif_info), GFP_KERNEL);
  
  	if (!subsys) {
  		mutex_unlock(&notif_add_lock);
  		return ERR_PTR(-EINVAL);
  	}
  
  	strlcpy(subsys->name, subsys_name, ARRAY_SIZE(subsys->name));
  
  	srcu_init_notifier_head(&subsys->subsys_notif_rcvr_list);
  
  	INIT_LIST_HEAD(&subsys->list);
  
  	mutex_lock(&notif_lock);
  	list_add_tail(&subsys->list, &subsystem_list);
  	mutex_unlock(&notif_lock);
  
  	#if defined(SUBSYS_RESTART_DEBUG)
  	subsys_notif_reg_test_notifier(subsys->name);
  	#endif
  
  	mutex_unlock(&notif_add_lock);
  
  done:
  	return subsys;
  }
  EXPORT_SYMBOL(subsys_notif_add_subsys);
  
  int subsys_notif_queue_notification(void *subsys_handle,
  					enum subsys_notif_type notif_type,
  					void *data)
  {
  	struct subsys_notif_info *subsys = subsys_handle;
  
  	if (!subsys)
  		return -EINVAL;
  
  	if (notif_type < 0 || notif_type >= SUBSYS_NOTIF_TYPE_COUNT)
  		return -EINVAL;
  
  	return srcu_notifier_call_chain(&subsys->subsys_notif_rcvr_list,
  				       notif_type, data);
  }
  EXPORT_SYMBOL(subsys_notif_queue_notification);
  
  #if defined(SUBSYS_RESTART_DEBUG)
  static const char *notif_to_string(enum subsys_notif_type notif_type)
  {
  	switch (notif_type) {
  
  	case	SUBSYS_BEFORE_SHUTDOWN:
  		return __stringify(SUBSYS_BEFORE_SHUTDOWN);
  
  	case	SUBSYS_AFTER_SHUTDOWN:
  		return __stringify(SUBSYS_AFTER_SHUTDOWN);
  
  	case	SUBSYS_BEFORE_POWERUP:
  		return __stringify(SUBSYS_BEFORE_POWERUP);
  
  	case	SUBSYS_AFTER_POWERUP:
  		return __stringify(SUBSYS_AFTER_POWERUP);
  
  	default:
  		return "unknown";
  	}
  }
  
  static int subsys_notifier_test_call(struct notifier_block *this,
  				  unsigned long code,
  				  void *data)
  {
  	switch (code) {
  
  	default:
  		pr_warn("%s: Notification %s from subsystem %pK\n",
  			__func__, notif_to_string(code), data);
  	break;
  
  	}
  
  	return NOTIFY_DONE;
  }
  
  static struct notifier_block nb = {
  	.notifier_call = subsys_notifier_test_call,
  };
  
  static void subsys_notif_reg_test_notifier(const char *subsys_name)
  {
  	void *handle = subsys_notif_register_notifier(subsys_name, &nb);
  
  	pr_warn("%s: Registered test notifier, handle=%pK",
  			__func__, handle);
  }
  #endif
  
  MODULE_DESCRIPTION("Subsystem Restart Notifier");
  MODULE_LICENSE("GPL v2");
  