reboot-mode.c 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. /*
  3. * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
  4. */
  5. #include <linux/device.h>
  6. #include <linux/init.h>
  7. #include <linux/kernel.h>
  8. #include <linux/module.h>
  9. #include <linux/of.h>
  10. #include <linux/reboot.h>
  11. #include <linux/reboot-mode.h>
  12. #define PREFIX "mode-"
  13. struct mode_info {
  14. const char *mode;
  15. u32 magic;
  16. struct list_head list;
  17. };
  18. static unsigned int get_reboot_mode_magic(struct reboot_mode_driver *reboot,
  19. const char *cmd)
  20. {
  21. const char *normal = "normal";
  22. struct mode_info *info;
  23. char cmd_[110];
  24. if (!cmd)
  25. cmd = normal;
  26. list_for_each_entry(info, &reboot->head, list)
  27. if (!strcmp(info->mode, cmd))
  28. return info->magic;
  29. /* try to match again, replacing characters impossible in DT */
  30. if (strscpy(cmd_, cmd, sizeof(cmd_)) == -E2BIG)
  31. return 0;
  32. strreplace(cmd_, ' ', '-');
  33. strreplace(cmd_, ',', '-');
  34. strreplace(cmd_, '/', '-');
  35. list_for_each_entry(info, &reboot->head, list)
  36. if (!strcmp(info->mode, cmd_))
  37. return info->magic;
  38. return 0;
  39. }
  40. static int reboot_mode_notify(struct notifier_block *this,
  41. unsigned long mode, void *cmd)
  42. {
  43. struct reboot_mode_driver *reboot;
  44. unsigned int magic;
  45. reboot = container_of(this, struct reboot_mode_driver, reboot_notifier);
  46. magic = get_reboot_mode_magic(reboot, cmd);
  47. if (magic)
  48. reboot->write(reboot, magic);
  49. return NOTIFY_DONE;
  50. }
  51. /**
  52. * reboot_mode_register - register a reboot mode driver
  53. * @reboot: reboot mode driver
  54. *
  55. * Returns: 0 on success or a negative error code on failure.
  56. */
  57. int reboot_mode_register(struct reboot_mode_driver *reboot)
  58. {
  59. struct mode_info *info;
  60. struct property *prop;
  61. struct device_node *np = reboot->dev->of_node;
  62. size_t len = strlen(PREFIX);
  63. int ret;
  64. INIT_LIST_HEAD(&reboot->head);
  65. for_each_property_of_node(np, prop) {
  66. if (strncmp(prop->name, PREFIX, len))
  67. continue;
  68. info = devm_kzalloc(reboot->dev, sizeof(*info), GFP_KERNEL);
  69. if (!info) {
  70. ret = -ENOMEM;
  71. goto error;
  72. }
  73. if (of_property_read_u32(np, prop->name, &info->magic)) {
  74. dev_err(reboot->dev, "reboot mode %s without magic number\n",
  75. info->mode);
  76. devm_kfree(reboot->dev, info);
  77. continue;
  78. }
  79. info->mode = kstrdup_const(prop->name + len, GFP_KERNEL);
  80. if (!info->mode) {
  81. ret = -ENOMEM;
  82. goto error;
  83. } else if (info->mode[0] == '\0') {
  84. kfree_const(info->mode);
  85. ret = -EINVAL;
  86. dev_err(reboot->dev, "invalid mode name(%s): too short!\n",
  87. prop->name);
  88. goto error;
  89. }
  90. list_add_tail(&info->list, &reboot->head);
  91. }
  92. reboot->reboot_notifier.notifier_call = reboot_mode_notify;
  93. register_reboot_notifier(&reboot->reboot_notifier);
  94. return 0;
  95. error:
  96. list_for_each_entry(info, &reboot->head, list)
  97. kfree_const(info->mode);
  98. return ret;
  99. }
  100. EXPORT_SYMBOL_GPL(reboot_mode_register);
  101. /**
  102. * reboot_mode_unregister - unregister a reboot mode driver
  103. * @reboot: reboot mode driver
  104. */
  105. int reboot_mode_unregister(struct reboot_mode_driver *reboot)
  106. {
  107. struct mode_info *info;
  108. unregister_reboot_notifier(&reboot->reboot_notifier);
  109. list_for_each_entry(info, &reboot->head, list)
  110. kfree_const(info->mode);
  111. return 0;
  112. }
  113. EXPORT_SYMBOL_GPL(reboot_mode_unregister);
  114. static void devm_reboot_mode_release(struct device *dev, void *res)
  115. {
  116. reboot_mode_unregister(*(struct reboot_mode_driver **)res);
  117. }
  118. /**
  119. * devm_reboot_mode_register() - resource managed reboot_mode_register()
  120. * @dev: device to associate this resource with
  121. * @reboot: reboot mode driver
  122. *
  123. * Returns: 0 on success or a negative error code on failure.
  124. */
  125. int devm_reboot_mode_register(struct device *dev,
  126. struct reboot_mode_driver *reboot)
  127. {
  128. struct reboot_mode_driver **dr;
  129. int rc;
  130. dr = devres_alloc(devm_reboot_mode_release, sizeof(*dr), GFP_KERNEL);
  131. if (!dr)
  132. return -ENOMEM;
  133. rc = reboot_mode_register(reboot);
  134. if (rc) {
  135. devres_free(dr);
  136. return rc;
  137. }
  138. *dr = reboot;
  139. devres_add(dev, dr);
  140. return 0;
  141. }
  142. EXPORT_SYMBOL_GPL(devm_reboot_mode_register);
  143. static int devm_reboot_mode_match(struct device *dev, void *res, void *data)
  144. {
  145. struct reboot_mode_driver **p = res;
  146. if (WARN_ON(!p || !*p))
  147. return 0;
  148. return *p == data;
  149. }
  150. /**
  151. * devm_reboot_mode_unregister() - resource managed reboot_mode_unregister()
  152. * @dev: device to associate this resource with
  153. * @reboot: reboot mode driver
  154. */
  155. void devm_reboot_mode_unregister(struct device *dev,
  156. struct reboot_mode_driver *reboot)
  157. {
  158. WARN_ON(devres_release(dev,
  159. devm_reboot_mode_release,
  160. devm_reboot_mode_match, reboot));
  161. }
  162. EXPORT_SYMBOL_GPL(devm_reboot_mode_unregister);
  163. MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
  164. MODULE_DESCRIPTION("System reboot mode core library");
  165. MODULE_LICENSE("GPL v2");