12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937 |
- /* Copyright (c) 2011-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.
- */
- #define pr_fmt(fmt) "subsys-restart: %s(): " fmt, __func__
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/uaccess.h>
- #include <linux/module.h>
- #include <linux/fs.h>
- #include <linux/delay.h>
- #include <linux/list.h>
- #include <linux/io.h>
- #include <linux/kthread.h>
- #include <linux/time.h>
- #include <linux/rtc.h>
- #include <linux/suspend.h>
- #include <linux/mutex.h>
- #include <linux/slab.h>
- #include <linux/spinlock.h>
- #include <linux/device.h>
- #include <linux/idr.h>
- #include <linux/debugfs.h>
- #include <linux/interrupt.h>
- #include <linux/of_gpio.h>
- #include <linux/cdev.h>
- #include <linux/platform_device.h>
- #include <soc/qcom/subsystem_restart.h>
- #include <soc/qcom/subsystem_notif.h>
- #include <soc/qcom/sysmon.h>
- #include <trace/events/trace_msm_pil_event.h>
- #include <asm/current.h>
- #include "peripheral-loader.h"
- #define DISABLE_SSR 0x9889deed
- /* If set to 0x9889deed, call to subsystem_restart_dev() returns immediately */
- static uint disable_restart_work;
- module_param(disable_restart_work, uint, 0644);
- static int enable_debug;
- module_param(enable_debug, int, 0644);
- /* The maximum shutdown timeout is the product of MAX_LOOPS and DELAY_MS. */
- #define SHUTDOWN_ACK_MAX_LOOPS 100
- #define SHUTDOWN_ACK_DELAY_MS 100
- /**
- * enum p_subsys_state - state of a subsystem (private)
- * @SUBSYS_NORMAL: subsystem is operating normally
- * @SUBSYS_CRASHED: subsystem has crashed and hasn't been shutdown
- * @SUBSYS_RESTARTING: subsystem has been shutdown and is now restarting
- *
- * The 'private' side of the subsytem state used to determine where in the
- * restart process the subsystem is.
- */
- enum p_subsys_state {
- SUBSYS_NORMAL,
- SUBSYS_CRASHED,
- SUBSYS_RESTARTING,
- };
- /**
- * enum subsys_state - state of a subsystem (public)
- * @SUBSYS_OFFLINING: subsystem is offlining
- * @SUBSYS_OFFLINE: subsystem is offline
- * @SUBSYS_ONLINE: subsystem is online
- *
- * The 'public' side of the subsytem state, exposed to userspace.
- */
- enum subsys_state {
- SUBSYS_OFFLINING,
- SUBSYS_OFFLINE,
- SUBSYS_ONLINE,
- };
- static const char * const subsys_states[] = {
- [SUBSYS_OFFLINING] = "OFFLINING",
- [SUBSYS_OFFLINE] = "OFFLINE",
- [SUBSYS_ONLINE] = "ONLINE",
- };
- static const char * const restart_levels[] = {
- [RESET_SOC] = "SYSTEM",
- [RESET_SUBSYS_COUPLED] = "RELATED",
- };
- /**
- * struct subsys_tracking - track state of a subsystem or restart order
- * @p_state: private state of subsystem/order
- * @state: public state of subsystem/order
- * @s_lock: protects p_state
- * @lock: protects subsystem/order callbacks and state
- *
- * Tracks the state of a subsystem or a set of subsystems (restart order).
- * Doing this avoids the need to grab each subsystem's lock and update
- * each subsystems state when restarting an order.
- */
- struct subsys_tracking {
- enum p_subsys_state p_state;
- spinlock_t s_lock;
- enum subsys_state state;
- struct mutex lock;
- };
- /**
- * struct subsys_soc_restart_order - subsystem restart order
- * @subsystem_list: names of subsystems in this restart order
- * @count: number of subsystems in order
- * @track: state tracking and locking
- * @subsys_ptrs: pointers to subsystems in this restart order
- */
- struct subsys_soc_restart_order {
- struct device_node **device_ptrs;
- int count;
- struct subsys_tracking track;
- struct subsys_device **subsys_ptrs;
- struct list_head list;
- };
- struct restart_log {
- struct timeval time;
- struct subsys_device *dev;
- struct list_head list;
- };
- /**
- * struct subsys_device - subsystem device
- * @desc: subsystem descriptor
- * @work: context for subsystem_restart_wq_func() for this device
- * @ssr_wlock: prevents suspend during subsystem_restart()
- * @wlname: name of wakeup source
- * @device_restart_work: work struct for device restart
- * @track: state tracking and locking
- * @notify: subsys notify handle
- * @dev: device
- * @owner: module that provides @desc
- * @count: reference count of subsystem_get()/subsystem_put()
- * @id: ida
- * @restart_level: restart level (0 - panic, 1 - related, 2 - independent, etc.)
- * @restart_order: order of other devices this devices restarts with
- * @crash_count: number of times the device has crashed
- * @dentry: debugfs directory for this device
- * @do_ramdump_on_put: ramdump on subsystem_put() if true
- * @err_ready: completion variable to record error ready from subsystem
- * @crashed: indicates if subsystem has crashed
- * @notif_state: current state of subsystem in terms of subsys notifications
- */
- struct subsys_device {
- struct subsys_desc *desc;
- struct work_struct work;
- struct wakeup_source ssr_wlock;
- char wlname[64];
- struct work_struct device_restart_work;
- struct subsys_tracking track;
- void *notify;
- struct device dev;
- struct module *owner;
- int count;
- int id;
- int restart_level;
- int crash_count;
- struct subsys_soc_restart_order *restart_order;
- #ifdef CONFIG_DEBUG_FS
- struct dentry *dentry;
- #endif
- bool do_ramdump_on_put;
- struct cdev char_dev;
- dev_t dev_no;
- struct completion err_ready;
- enum crash_status crashed;
- int notif_state;
- struct list_head list;
- };
- static struct subsys_device *to_subsys(struct device *d)
- {
- return container_of(d, struct subsys_device, dev);
- }
- void complete_err_ready(struct subsys_device *subsys)
- {
- complete(&subsys->err_ready);
- }
- static struct subsys_tracking *subsys_get_track(struct subsys_device *subsys)
- {
- struct subsys_soc_restart_order *order = subsys->restart_order;
- if (order)
- return &order->track;
- else
- return &subsys->track;
- }
- static ssize_t name_show(struct device *dev, struct device_attribute *attr,
- char *buf)
- {
- return snprintf(buf, PAGE_SIZE, "%s\n", to_subsys(dev)->desc->name);
- }
- static ssize_t state_show(struct device *dev, struct device_attribute *attr,
- char *buf)
- {
- enum subsys_state state = to_subsys(dev)->track.state;
- return snprintf(buf, PAGE_SIZE, "%s\n", subsys_states[state]);
- }
- static ssize_t crash_count_show(struct device *dev,
- struct device_attribute *attr, char *buf)
- {
- return snprintf(buf, PAGE_SIZE, "%d\n", to_subsys(dev)->crash_count);
- }
- static ssize_t crash_reason_show(struct device *dev,
- struct device_attribute *attr, char *buf)
- {
- return snprintf(buf, PAGE_SIZE, "%s\n",
- to_subsys(dev)->desc->last_crash_reason);
- }
- static ssize_t crash_timestamp_show(struct device *dev,
- struct device_attribute *attr, char *buf)
- {
- return snprintf(buf, PAGE_SIZE, "%s\n",
- to_subsys(dev)->desc->last_crash_timestamp);
- }
- static ssize_t
- restart_level_show(struct device *dev, struct device_attribute *attr, char *buf)
- {
- int level = to_subsys(dev)->restart_level;
- return snprintf(buf, PAGE_SIZE, "%s\n", restart_levels[level]);
- }
- static ssize_t restart_level_store(struct device *dev,
- struct device_attribute *attr, const char *buf, size_t count)
- {
- struct subsys_device *subsys = to_subsys(dev);
- const char *p;
- int i, orig_count = count;
- p = memchr(buf, '\n', count);
- if (p)
- count = p - buf;
- for (i = 0; i < ARRAY_SIZE(restart_levels); i++)
- if (!strncasecmp(buf, restart_levels[i], count)) {
- subsys->restart_level = i;
- return orig_count;
- }
- return -EPERM;
- }
- static ssize_t firmware_name_show(struct device *dev,
- struct device_attribute *attr, char *buf)
- {
- return snprintf(buf, PAGE_SIZE, "%s\n", to_subsys(dev)->desc->fw_name);
- }
- static ssize_t firmware_name_store(struct device *dev,
- struct device_attribute *attr, const char *buf, size_t count)
- {
- struct subsys_device *subsys = to_subsys(dev);
- struct subsys_tracking *track = subsys_get_track(subsys);
- const char *p;
- int orig_count = count;
- p = memchr(buf, '\n', count);
- if (p)
- count = p - buf;
- pr_info("Changing subsys fw_name to %s\n", buf);
- mutex_lock(&track->lock);
- strlcpy(subsys->desc->fw_name, buf,
- min(count + 1, sizeof(subsys->desc->fw_name)));
- mutex_unlock(&track->lock);
- return orig_count;
- }
- static ssize_t system_debug_show(struct device *dev,
- struct device_attribute *attr, char *buf)
- {
- struct subsys_device *subsys = to_subsys(dev);
- char p[6] = "set";
- if (!subsys->desc->system_debug)
- strlcpy(p, "reset", sizeof(p));
- return snprintf(buf, PAGE_SIZE, "%s\n", p);
- }
- static ssize_t system_debug_store(struct device *dev,
- struct device_attribute *attr, const char *buf,
- size_t count)
- {
- struct subsys_device *subsys = to_subsys(dev);
- const char *p;
- int orig_count = count;
- p = memchr(buf, '\n', count);
- if (p)
- count = p - buf;
- if (!strncasecmp(buf, "set", count))
- subsys->desc->system_debug = true;
- else if (!strncasecmp(buf, "reset", count))
- subsys->desc->system_debug = false;
- else
- return -EPERM;
- return orig_count;
- }
- int subsys_get_restart_level(struct subsys_device *dev)
- {
- return dev->restart_level;
- }
- EXPORT_SYMBOL(subsys_get_restart_level);
- static void subsys_set_state(struct subsys_device *subsys,
- enum subsys_state state)
- {
- unsigned long flags;
- spin_lock_irqsave(&subsys->track.s_lock, flags);
- if (subsys->track.state != state) {
- subsys->track.state = state;
- spin_unlock_irqrestore(&subsys->track.s_lock, flags);
- sysfs_notify(&subsys->dev.kobj, NULL, "state");
- return;
- }
- spin_unlock_irqrestore(&subsys->track.s_lock, flags);
- }
- /**
- * subsytem_default_online() - Mark a subsystem as online by default
- * @dev: subsystem to mark as online
- *
- * Marks a subsystem as "online" without increasing the reference count
- * on the subsystem. This is typically used by subsystems that are already
- * online when the kernel boots up.
- */
- void subsys_default_online(struct subsys_device *dev)
- {
- subsys_set_state(dev, SUBSYS_ONLINE);
- }
- EXPORT_SYMBOL(subsys_default_online);
- static struct device_attribute subsys_attrs[] = {
- __ATTR_RO(name),
- __ATTR_RO(state),
- __ATTR_RO(crash_count),
- __ATTR_RO(crash_reason),
- __ATTR_RO(crash_timestamp),
- __ATTR(restart_level, 0644, restart_level_show, restart_level_store),
- __ATTR(firmware_name, 0644, firmware_name_show, firmware_name_store),
- __ATTR(system_debug, 0644, system_debug_show, system_debug_store),
- __ATTR_NULL,
- };
- static struct bus_type subsys_bus_type = {
- .name = "msm_subsys",
- .dev_attrs = subsys_attrs,
- };
- static DEFINE_IDA(subsys_ida);
- static int enable_ramdumps;
- module_param(enable_ramdumps, int, 0644);
- static int enable_mini_ramdumps;
- module_param(enable_mini_ramdumps, int, 0644);
- struct workqueue_struct *ssr_wq;
- static struct class *char_class;
- static LIST_HEAD(restart_log_list);
- static LIST_HEAD(subsys_list);
- static LIST_HEAD(ssr_order_list);
- static DEFINE_MUTEX(soc_order_reg_lock);
- static DEFINE_MUTEX(restart_log_mutex);
- static DEFINE_MUTEX(subsys_list_lock);
- static DEFINE_MUTEX(char_device_lock);
- static DEFINE_MUTEX(ssr_order_mutex);
- static struct subsys_soc_restart_order *
- update_restart_order(struct subsys_device *dev)
- {
- int i;
- struct subsys_soc_restart_order *order;
- struct device_node *device = dev->desc->dev->of_node;
- mutex_lock(&soc_order_reg_lock);
- list_for_each_entry(order, &ssr_order_list, list) {
- for (i = 0; i < order->count; i++) {
- if (order->device_ptrs[i] == device) {
- order->subsys_ptrs[i] = dev;
- goto found;
- }
- }
- }
- order = NULL;
- found:
- mutex_unlock(&soc_order_reg_lock);
- return order;
- }
- static int max_restarts;
- module_param(max_restarts, int, 0644);
- static long max_history_time = 3600;
- module_param(max_history_time, long, 0644);
- static void do_epoch_check(struct subsys_device *dev)
- {
- int n = 0;
- struct timeval *time_first = NULL, *curr_time;
- struct restart_log *r_log, *temp;
- static int max_restarts_check;
- static long max_history_time_check;
- mutex_lock(&restart_log_mutex);
- max_restarts_check = max_restarts;
- max_history_time_check = max_history_time;
- /* Check if epoch checking is enabled */
- if (!max_restarts_check)
- goto out;
- r_log = kmalloc(sizeof(struct restart_log), GFP_KERNEL);
- if (!r_log)
- goto out;
- r_log->dev = dev;
- do_gettimeofday(&r_log->time);
- curr_time = &r_log->time;
- INIT_LIST_HEAD(&r_log->list);
- list_add_tail(&r_log->list, &restart_log_list);
- list_for_each_entry_safe(r_log, temp, &restart_log_list, list) {
- if ((curr_time->tv_sec - r_log->time.tv_sec) >
- max_history_time_check) {
- pr_debug("Deleted node with restart_time = %ld\n",
- r_log->time.tv_sec);
- list_del(&r_log->list);
- kfree(r_log);
- continue;
- }
- if (!n) {
- time_first = &r_log->time;
- pr_debug("Time_first: %ld\n", time_first->tv_sec);
- }
- n++;
- pr_debug("Restart_time: %ld\n", r_log->time.tv_sec);
- }
- if (time_first && n >= max_restarts_check) {
- if ((curr_time->tv_sec - time_first->tv_sec) <
- max_history_time_check)
- panic("Subsystems have crashed %d times in less than %ld seconds!",
- max_restarts_check, max_history_time_check);
- }
- out:
- mutex_unlock(&restart_log_mutex);
- }
- static int is_ramdump_enabled(struct subsys_device *dev)
- {
- if (dev->desc->ramdump_disable_gpio)
- return !dev->desc->ramdump_disable;
- return enable_ramdumps;
- }
- static void send_sysmon_notif(struct subsys_device *dev)
- {
- struct subsys_device *subsys;
- mutex_lock(&subsys_list_lock);
- list_for_each_entry(subsys, &subsys_list, list)
- if ((subsys->notif_state > 0) && (subsys != dev))
- sysmon_send_event(dev->desc, subsys->desc,
- subsys->notif_state);
- mutex_unlock(&subsys_list_lock);
- }
- static int for_each_subsys_device(struct subsys_device **list,
- unsigned int count, void *data,
- int (*fn)(struct subsys_device *, void *))
- {
- int ret;
- while (count--) {
- struct subsys_device *dev = *list++;
- if (!dev)
- continue;
- ret = fn(dev, data);
- if (ret)
- return ret;
- }
- return 0;
- }
- static void notify_each_subsys_device(struct subsys_device **list,
- unsigned int count,
- enum subsys_notif_type notif, void *data)
- {
- struct subsys_device *subsys;
- while (count--) {
- struct subsys_device *dev = *list++;
- struct notif_data notif_data;
- struct platform_device *pdev;
- if (!dev)
- continue;
- pdev = container_of(dev->desc->dev, struct platform_device,
- dev);
- dev->notif_state = notif;
- mutex_lock(&subsys_list_lock);
- list_for_each_entry(subsys, &subsys_list, list)
- if (dev != subsys &&
- subsys->track.state == SUBSYS_ONLINE)
- sysmon_send_event(subsys->desc, dev->desc,
- notif);
- mutex_unlock(&subsys_list_lock);
- if (notif == SUBSYS_AFTER_POWERUP &&
- dev->track.state == SUBSYS_ONLINE)
- send_sysmon_notif(dev);
- notif_data.crashed = subsys_get_crash_status(dev);
- notif_data.enable_ramdump = is_ramdump_enabled(dev);
- notif_data.enable_mini_ramdumps = enable_mini_ramdumps;
- notif_data.no_auth = dev->desc->no_auth;
- notif_data.pdev = pdev;
- trace_pil_notif("before_send_notif", notif, dev->desc->fw_name);
- subsys_notif_queue_notification(dev->notify, notif,
- ¬if_data);
- trace_pil_notif("after_send_notif", notif, dev->desc->fw_name);
- }
- }
- static void enable_all_irqs(struct subsys_device *dev)
- {
- if (dev->desc->err_ready_irq)
- enable_irq(dev->desc->err_ready_irq);
- if (dev->desc->wdog_bite_irq && dev->desc->wdog_bite_handler) {
- enable_irq(dev->desc->wdog_bite_irq);
- irq_set_irq_wake(dev->desc->wdog_bite_irq, 1);
- }
- if (dev->desc->err_fatal_irq && dev->desc->err_fatal_handler)
- enable_irq(dev->desc->err_fatal_irq);
- if (dev->desc->stop_ack_irq && dev->desc->stop_ack_handler)
- enable_irq(dev->desc->stop_ack_irq);
- if (dev->desc->generic_irq && dev->desc->generic_handler) {
- enable_irq(dev->desc->generic_irq);
- irq_set_irq_wake(dev->desc->generic_irq, 1);
- }
- }
- static void disable_all_irqs(struct subsys_device *dev)
- {
- if (dev->desc->err_ready_irq)
- disable_irq(dev->desc->err_ready_irq);
- if (dev->desc->wdog_bite_irq && dev->desc->wdog_bite_handler) {
- disable_irq(dev->desc->wdog_bite_irq);
- irq_set_irq_wake(dev->desc->wdog_bite_irq, 0);
- }
- if (dev->desc->err_fatal_irq && dev->desc->err_fatal_handler)
- disable_irq(dev->desc->err_fatal_irq);
- if (dev->desc->stop_ack_irq && dev->desc->stop_ack_handler)
- disable_irq(dev->desc->stop_ack_irq);
- if (dev->desc->generic_irq && dev->desc->generic_handler) {
- disable_irq(dev->desc->generic_irq);
- irq_set_irq_wake(dev->desc->generic_irq, 0);
- }
- }
- static int wait_for_err_ready(struct subsys_device *subsys)
- {
- int ret;
- /*
- * If subsys is using generic_irq in which case err_ready_irq will be 0,
- * don't return.
- */
- if ((subsys->desc->generic_irq <= 0 && !subsys->desc->err_ready_irq) ||
- enable_debug == 1 || is_timeout_disabled())
- return 0;
- ret = wait_for_completion_timeout(&subsys->err_ready,
- msecs_to_jiffies(10000));
- if (!ret) {
- pr_err("[%s]: Error ready timed out\n", subsys->desc->name);
- return -ETIMEDOUT;
- }
- return 0;
- }
- static int subsystem_shutdown(struct subsys_device *dev, void *data)
- {
- const char *name = dev->desc->name;
- char *timestamp = dev->desc->last_crash_timestamp;
- int ret;
- struct timespec ts_rtc;
- struct rtc_time tm;
- pr_info("[%s:%d]: Shutting down %s\n",
- current->comm, current->pid, name);
- ret = dev->desc->shutdown(dev->desc, true);
- if (ret < 0) {
- if (!dev->desc->ignore_ssr_failure) {
- panic("subsys-restart: [%s:%d]: Failed to shutdown %s!",
- current->comm, current->pid, name);
- } else {
- pr_err("Shutdown failure on %s\n", name);
- return ret;
- }
- }
- /* record crash time */
- getnstimeofday(&ts_rtc);
- rtc_time_to_tm(ts_rtc.tv_sec - (sys_tz.tz_minuteswest * 60), &tm);
- snprintf(timestamp, MAX_CRASH_TIMESTAMP_LEN,
- "%d-%02d-%02d_%02d-%02d-%02d",
- tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
- tm.tm_hour, tm.tm_min, tm.tm_sec);
- dev->crash_count++;
- subsys_set_state(dev, SUBSYS_OFFLINE);
- disable_all_irqs(dev);
- return 0;
- }
- static int subsystem_ramdump(struct subsys_device *dev, void *data)
- {
- const char *name = dev->desc->name;
- if (dev->desc->ramdump)
- if (dev->desc->ramdump(is_ramdump_enabled(dev), dev->desc) < 0)
- pr_warn("%s[%s:%d]: Ramdump failed.\n",
- name, current->comm, current->pid);
- dev->do_ramdump_on_put = false;
- return 0;
- }
- static int subsystem_free_memory(struct subsys_device *dev, void *data)
- {
- if (dev->desc->free_memory)
- dev->desc->free_memory(dev->desc);
- return 0;
- }
- static int subsystem_powerup(struct subsys_device *dev, void *data)
- {
- const char *name = dev->desc->name;
- int ret;
- pr_info("[%s:%d]: Powering up %s\n", current->comm, current->pid, name);
- init_completion(&dev->err_ready);
- ret = dev->desc->powerup(dev->desc);
- if (ret < 0) {
- notify_each_subsys_device(&dev, 1, SUBSYS_POWERUP_FAILURE,
- NULL);
- if (system_state == SYSTEM_RESTART
- || system_state == SYSTEM_POWER_OFF)
- WARN(1, "SSR aborted: %s, system reboot/shutdown is under way\n",
- name);
- else if (!dev->desc->ignore_ssr_failure)
- panic("[%s:%d]: Powerup error: %s!",
- current->comm, current->pid, name);
- else
- pr_err("Powerup failure on %s\n", name);
- return ret;
- }
- enable_all_irqs(dev);
- ret = wait_for_err_ready(dev);
- if (ret) {
- notify_each_subsys_device(&dev, 1, SUBSYS_POWERUP_FAILURE,
- NULL);
- if (!dev->desc->ignore_ssr_failure)
- panic("[%s:%d]: Timed out waiting for error ready: %s!",
- current->comm, current->pid, name);
- else
- return ret;
- }
- subsys_set_state(dev, SUBSYS_ONLINE);
- subsys_set_crash_status(dev, CRASH_STATUS_NO_CRASH);
- return 0;
- }
- static int __find_subsys(struct device *dev, void *data)
- {
- struct subsys_device *subsys = to_subsys(dev);
- return !strcmp(subsys->desc->name, data);
- }
- static struct subsys_device *find_subsys(const char *str)
- {
- struct device *dev;
- if (!str)
- return NULL;
- dev = bus_find_device(&subsys_bus_type, NULL, (void *)str,
- __find_subsys);
- return dev ? to_subsys(dev) : NULL;
- }
- static int subsys_start(struct subsys_device *subsys)
- {
- int ret;
- notify_each_subsys_device(&subsys, 1, SUBSYS_BEFORE_POWERUP,
- NULL);
- init_completion(&subsys->err_ready);
- ret = subsys->desc->powerup(subsys->desc);
- if (ret) {
- notify_each_subsys_device(&subsys, 1, SUBSYS_POWERUP_FAILURE,
- NULL);
- return ret;
- }
- enable_all_irqs(subsys);
- if (subsys->desc->is_not_loadable) {
- subsys_set_state(subsys, SUBSYS_ONLINE);
- return 0;
- }
- ret = wait_for_err_ready(subsys);
- if (ret) {
- /* pil-boot succeeded but we need to shutdown
- * the device because error ready timed out.
- */
- notify_each_subsys_device(&subsys, 1, SUBSYS_POWERUP_FAILURE,
- NULL);
- subsys->desc->shutdown(subsys->desc, false);
- disable_all_irqs(subsys);
- return ret;
- }
- subsys_set_state(subsys, SUBSYS_ONLINE);
- notify_each_subsys_device(&subsys, 1, SUBSYS_AFTER_POWERUP,
- NULL);
- return ret;
- }
- static void subsys_stop(struct subsys_device *subsys)
- {
- const char *name = subsys->desc->name;
- notify_each_subsys_device(&subsys, 1, SUBSYS_BEFORE_SHUTDOWN, NULL);
- if (!of_property_read_bool(subsys->desc->dev->of_node,
- "qcom,pil-force-shutdown")) {
- subsys_set_state(subsys, SUBSYS_OFFLINING);
- subsys->desc->sysmon_shutdown_ret =
- sysmon_send_shutdown(subsys->desc);
- if (subsys->desc->sysmon_shutdown_ret)
- pr_debug("Graceful shutdown failed for %s\n", name);
- }
- subsys->desc->shutdown(subsys->desc, false);
- subsys_set_state(subsys, SUBSYS_OFFLINE);
- disable_all_irqs(subsys);
- notify_each_subsys_device(&subsys, 1, SUBSYS_AFTER_SHUTDOWN, NULL);
- }
- int subsystem_set_fwname(const char *name, const char *fw_name)
- {
- struct subsys_device *subsys;
- if (!name)
- return -EINVAL;
- if (!fw_name)
- return -EINVAL;
- subsys = find_subsys(name);
- if (!subsys)
- return -EINVAL;
- pr_debug("Changing subsys [%s] fw_name to [%s]\n", name, fw_name);
- strlcpy(subsys->desc->fw_name, fw_name,
- sizeof(subsys->desc->fw_name));
- return 0;
- }
- EXPORT_SYMBOL(subsystem_set_fwname);
- int wait_for_shutdown_ack(struct subsys_desc *desc)
- {
- int count;
- struct subsys_device *dev;
- if (!desc || !desc->shutdown_ack_gpio)
- return 0;
- dev = find_subsys(desc->name);
- if (!dev)
- return 0;
- for (count = SHUTDOWN_ACK_MAX_LOOPS; count > 0; count--) {
- if (gpio_get_value(desc->shutdown_ack_gpio))
- return count;
- else if (subsys_get_crash_status(dev))
- break;
- msleep(SHUTDOWN_ACK_DELAY_MS);
- }
- pr_err("[%s]: Timed out waiting for shutdown ack\n", desc->name);
- return -ETIMEDOUT;
- }
- EXPORT_SYMBOL(wait_for_shutdown_ack);
- void *__subsystem_get(const char *name, const char *fw_name)
- {
- struct subsys_device *subsys;
- struct subsys_device *subsys_d;
- int ret;
- void *retval;
- struct subsys_tracking *track;
- if (!name)
- return NULL;
- subsys = retval = find_subsys(name);
- if (!subsys)
- return ERR_PTR(-ENODEV);
- if (!try_module_get(subsys->owner)) {
- retval = ERR_PTR(-ENODEV);
- goto err_module;
- }
- subsys_d = subsystem_get(subsys->desc->depends_on);
- if (IS_ERR(subsys_d)) {
- retval = subsys_d;
- goto err_depends;
- }
- track = subsys_get_track(subsys);
- mutex_lock(&track->lock);
- if (!subsys->count) {
- if (fw_name) {
- pr_info("Changing subsys fw_name to %s\n", fw_name);
- strlcpy(subsys->desc->fw_name, fw_name,
- sizeof(subsys->desc->fw_name));
- }
- ret = subsys_start(subsys);
- if (ret) {
- retval = ERR_PTR(ret);
- goto err_start;
- }
- }
- subsys->count++;
- mutex_unlock(&track->lock);
- return retval;
- err_start:
- mutex_unlock(&track->lock);
- subsystem_put(subsys_d);
- err_depends:
- module_put(subsys->owner);
- err_module:
- put_device(&subsys->dev);
- return retval;
- }
- /**
- * subsytem_get() - Boot a subsystem
- * @name: pointer to a string containing the name of the subsystem to boot
- *
- * This function returns a pointer if it succeeds. If an error occurs an
- * ERR_PTR is returned.
- *
- * If this feature is disable, the value %NULL will be returned.
- */
- void *subsystem_get(const char *name)
- {
- return __subsystem_get(name, NULL);
- }
- EXPORT_SYMBOL(subsystem_get);
- /**
- * subsystem_get_with_fwname() - Boot a subsystem using the firmware name passed
- * @name: pointer to a string containing the name of the subsystem to boot
- * @fw_name: pointer to a string containing the subsystem firmware image name
- *
- * This function returns a pointer if it succeeds. If an error occurs an
- * ERR_PTR is returned.
- *
- * If this feature is disable, the value %NULL will be returned.
- */
- void *subsystem_get_with_fwname(const char *name, const char *fw_name)
- {
- return __subsystem_get(name, fw_name);
- }
- EXPORT_SYMBOL(subsystem_get_with_fwname);
- /**
- * subsystem_put() - Shutdown a subsystem
- * @peripheral_handle: pointer from a previous call to subsystem_get()
- *
- * This doesn't imply that a subsystem is shutdown until all callers of
- * subsystem_get() have called subsystem_put().
- */
- void subsystem_put(void *subsystem)
- {
- struct subsys_device *subsys_d, *subsys = subsystem;
- struct subsys_tracking *track;
- if (IS_ERR_OR_NULL(subsys))
- return;
- track = subsys_get_track(subsys);
- mutex_lock(&track->lock);
- if (WARN(!subsys->count, "%s: %s: Reference count mismatch\n",
- subsys->desc->name, __func__))
- goto err_out;
- if (!--subsys->count) {
- subsys_stop(subsys);
- if (subsys->do_ramdump_on_put)
- subsystem_ramdump(subsys, NULL);
- subsystem_free_memory(subsys, NULL);
- }
- mutex_unlock(&track->lock);
- subsys_d = find_subsys(subsys->desc->depends_on);
- if (subsys_d) {
- subsystem_put(subsys_d);
- put_device(&subsys_d->dev);
- }
- module_put(subsys->owner);
- put_device(&subsys->dev);
- return;
- err_out:
- mutex_unlock(&track->lock);
- }
- EXPORT_SYMBOL(subsystem_put);
- static void subsystem_restart_wq_func(struct work_struct *work)
- {
- struct subsys_device *dev = container_of(work,
- struct subsys_device, work);
- struct subsys_device **list;
- struct subsys_desc *desc = dev->desc;
- struct subsys_soc_restart_order *order = dev->restart_order;
- struct subsys_tracking *track;
- unsigned int count;
- unsigned long flags;
- int ret;
- /*
- * It's OK to not take the registration lock at this point.
- * This is because the subsystem list inside the relevant
- * restart order is not being traversed.
- */
- if (order) {
- list = order->subsys_ptrs;
- count = order->count;
- track = &order->track;
- } else {
- list = &dev;
- count = 1;
- track = &dev->track;
- }
- /*
- * If a system reboot/shutdown is under way, ignore subsystem errors.
- * However, print a message so that we know that a subsystem behaved
- * unexpectedly here.
- */
- if (system_state == SYSTEM_RESTART
- || system_state == SYSTEM_POWER_OFF) {
- WARN(1, "SSR aborted: %s, system reboot/shutdown is under way\n",
- desc->name);
- return;
- }
- mutex_lock(&track->lock);
- do_epoch_check(dev);
- if (dev->track.state == SUBSYS_OFFLINE) {
- mutex_unlock(&track->lock);
- WARN(1, "SSR aborted: %s subsystem not online\n", desc->name);
- return;
- }
- /*
- * It's necessary to take the registration lock because the subsystem
- * list in the SoC restart order will be traversed and it shouldn't be
- * changed until _this_ restart sequence completes.
- */
- mutex_lock(&soc_order_reg_lock);
- pr_debug("[%s:%d]: Starting restart sequence for %s\n",
- current->comm, current->pid, desc->name);
- notify_each_subsys_device(list, count, SUBSYS_BEFORE_SHUTDOWN, NULL);
- ret = for_each_subsys_device(list, count, NULL, subsystem_shutdown);
- if (ret)
- goto err;
- notify_each_subsys_device(list, count, SUBSYS_AFTER_SHUTDOWN, NULL);
- notify_each_subsys_device(list, count, SUBSYS_RAMDUMP_NOTIFICATION,
- NULL);
- spin_lock_irqsave(&track->s_lock, flags);
- track->p_state = SUBSYS_RESTARTING;
- spin_unlock_irqrestore(&track->s_lock, flags);
- /* Collect ram dumps for all subsystems in order here */
- for_each_subsys_device(list, count, NULL, subsystem_ramdump);
- for_each_subsys_device(list, count, NULL, subsystem_free_memory);
- notify_each_subsys_device(list, count, SUBSYS_BEFORE_POWERUP, NULL);
- ret = for_each_subsys_device(list, count, NULL, subsystem_powerup);
- if (ret)
- goto err;
- notify_each_subsys_device(list, count, SUBSYS_AFTER_POWERUP, NULL);
- pr_info("[%s:%d]: Restart sequence for %s completed.\n",
- current->comm, current->pid, desc->name);
- err:
- /* Reset subsys count */
- if (ret)
- dev->count = 0;
- mutex_unlock(&soc_order_reg_lock);
- mutex_unlock(&track->lock);
- spin_lock_irqsave(&track->s_lock, flags);
- track->p_state = SUBSYS_NORMAL;
- __pm_relax(&dev->ssr_wlock);
- spin_unlock_irqrestore(&track->s_lock, flags);
- }
- static void __subsystem_restart_dev(struct subsys_device *dev)
- {
- struct subsys_desc *desc = dev->desc;
- const char *name = dev->desc->name;
- struct subsys_tracking *track;
- unsigned long flags;
- pr_debug("Restarting %s [level=%s]!\n", desc->name,
- restart_levels[dev->restart_level]);
- track = subsys_get_track(dev);
- /*
- * Allow drivers to call subsystem_restart{_dev}() as many times as
- * they want up until the point where the subsystem is shutdown.
- */
- spin_lock_irqsave(&track->s_lock, flags);
- if (track->p_state != SUBSYS_CRASHED &&
- dev->track.state == SUBSYS_ONLINE) {
- if (track->p_state != SUBSYS_RESTARTING) {
- track->p_state = SUBSYS_CRASHED;
- __pm_stay_awake(&dev->ssr_wlock);
- queue_work(ssr_wq, &dev->work);
- } else {
- panic("Subsystem %s crashed during SSR!", name);
- }
- } else
- WARN(dev->track.state == SUBSYS_OFFLINE,
- "SSR aborted: %s subsystem not online\n", name);
- spin_unlock_irqrestore(&track->s_lock, flags);
- }
- static void device_restart_work_hdlr(struct work_struct *work)
- {
- struct subsys_device *dev = container_of(work, struct subsys_device,
- device_restart_work);
- notify_each_subsys_device(&dev, 1, SUBSYS_SOC_RESET, NULL);
- /*
- * Temporary workaround until ramdump userspace application calls
- * sync() and fclose() on attempting the dump.
- */
- msleep(100);
- panic("subsys-restart: Resetting the SoC - %s crashed.",
- dev->desc->name);
- }
- int subsystem_restart_dev(struct subsys_device *dev)
- {
- const char *name;
- if (!get_device(&dev->dev))
- return -ENODEV;
- if (!try_module_get(dev->owner)) {
- put_device(&dev->dev);
- return -ENODEV;
- }
- name = dev->desc->name;
- /*
- * If a system reboot/shutdown is underway, ignore subsystem errors.
- * However, print a message so that we know that a subsystem behaved
- * unexpectedly here.
- */
- if (system_state == SYSTEM_RESTART
- || system_state == SYSTEM_POWER_OFF) {
- pr_err("%s crashed during a system poweroff/shutdown.\n", name);
- return -EBUSY;
- }
- pr_info("Restart sequence requested for %s, restart_level = %s.\n",
- name, restart_levels[dev->restart_level]);
- if (disable_restart_work == DISABLE_SSR) {
- pr_warn("subsys-restart: Ignoring restart request for %s\n",
- name);
- return 0;
- }
- switch (dev->restart_level) {
- case RESET_SUBSYS_COUPLED:
- __subsystem_restart_dev(dev);
- break;
- case RESET_SOC:
- __pm_stay_awake(&dev->ssr_wlock);
- schedule_work(&dev->device_restart_work);
- return 0;
- default:
- panic("subsys-restart: Unknown restart level!\n");
- break;
- }
- module_put(dev->owner);
- put_device(&dev->dev);
- return 0;
- }
- EXPORT_SYMBOL(subsystem_restart_dev);
- int subsystem_restart(const char *name)
- {
- int ret;
- struct subsys_device *dev = find_subsys(name);
- if (!dev)
- return -ENODEV;
- ret = subsystem_restart_dev(dev);
- put_device(&dev->dev);
- return ret;
- }
- EXPORT_SYMBOL(subsystem_restart);
- int subsystem_crashed(const char *name)
- {
- struct subsys_device *dev = find_subsys(name);
- struct subsys_tracking *track;
- if (!dev)
- return -ENODEV;
- if (!get_device(&dev->dev))
- return -ENODEV;
- track = subsys_get_track(dev);
- mutex_lock(&track->lock);
- dev->do_ramdump_on_put = true;
- /*
- * TODO: Make this work with multiple consumers where one is calling
- * subsystem_restart() and another is calling this function. To do
- * so would require updating private state, etc.
- */
- mutex_unlock(&track->lock);
- put_device(&dev->dev);
- return 0;
- }
- EXPORT_SYMBOL(subsystem_crashed);
- void subsys_set_crash_status(struct subsys_device *dev,
- enum crash_status crashed)
- {
- dev->crashed = crashed;
- }
- EXPORT_SYMBOL(subsys_set_crash_status);
- enum crash_status subsys_get_crash_status(struct subsys_device *dev)
- {
- return dev->crashed;
- }
- static struct subsys_device *desc_to_subsys(struct device *d)
- {
- struct subsys_device *device, *subsys_dev = 0;
- mutex_lock(&subsys_list_lock);
- list_for_each_entry(device, &subsys_list, list)
- if (device->desc->dev == d)
- subsys_dev = device;
- mutex_unlock(&subsys_list_lock);
- return subsys_dev;
- }
- void notify_proxy_vote(struct device *device)
- {
- struct subsys_device *dev = desc_to_subsys(device);
- if (dev)
- notify_each_subsys_device(&dev, 1, SUBSYS_PROXY_VOTE, NULL);
- }
- void notify_proxy_unvote(struct device *device)
- {
- struct subsys_device *dev = desc_to_subsys(device);
- if (dev)
- notify_each_subsys_device(&dev, 1, SUBSYS_PROXY_UNVOTE, NULL);
- }
- #ifdef CONFIG_DEBUG_FS
- static ssize_t subsys_debugfs_read(struct file *filp, char __user *ubuf,
- size_t cnt, loff_t *ppos)
- {
- int r;
- char buf[40];
- struct subsys_device *subsys = filp->private_data;
- r = snprintf(buf, sizeof(buf), "%d\n", subsys->count);
- return simple_read_from_buffer(ubuf, cnt, ppos, buf, r);
- }
- static ssize_t subsys_debugfs_write(struct file *filp,
- const char __user *ubuf, size_t cnt, loff_t *ppos)
- {
- struct subsys_device *subsys = filp->private_data;
- char buf[10];
- char *cmp;
- cnt = min(cnt, sizeof(buf) - 1);
- if (copy_from_user(&buf, ubuf, cnt))
- return -EFAULT;
- buf[cnt] = '\0';
- cmp = strstrip(buf);
- if (!strcmp(cmp, "restart")) {
- if (subsystem_restart_dev(subsys))
- return -EIO;
- } else if (!strcmp(cmp, "get")) {
- if (subsystem_get(subsys->desc->name))
- return -EIO;
- } else if (!strcmp(cmp, "put")) {
- subsystem_put(subsys);
- } else {
- return -EINVAL;
- }
- return cnt;
- }
- static const struct file_operations subsys_debugfs_fops = {
- .open = simple_open,
- .read = subsys_debugfs_read,
- .write = subsys_debugfs_write,
- };
- static struct dentry *subsys_base_dir;
- static int __init subsys_debugfs_init(void)
- {
- subsys_base_dir = debugfs_create_dir("msm_subsys", NULL);
- return !subsys_base_dir ? -ENOMEM : 0;
- }
- static void subsys_debugfs_exit(void)
- {
- debugfs_remove_recursive(subsys_base_dir);
- }
- static int subsys_debugfs_add(struct subsys_device *subsys)
- {
- if (!subsys_base_dir)
- return -ENOMEM;
- subsys->dentry = debugfs_create_file(subsys->desc->name,
- 0644, subsys_base_dir,
- subsys, &subsys_debugfs_fops);
- return !subsys->dentry ? -ENOMEM : 0;
- }
- static void subsys_debugfs_remove(struct subsys_device *subsys)
- {
- debugfs_remove(subsys->dentry);
- }
- #else
- static int __init subsys_debugfs_init(void) { return 0; };
- static void subsys_debugfs_exit(void) { }
- static int subsys_debugfs_add(struct subsys_device *subsys) { return 0; }
- static void subsys_debugfs_remove(struct subsys_device *subsys) { }
- #endif
- static int subsys_device_open(struct inode *inode, struct file *file)
- {
- struct subsys_device *device, *subsys_dev = 0;
- void *retval;
- mutex_lock(&subsys_list_lock);
- list_for_each_entry(device, &subsys_list, list)
- if (MINOR(device->dev_no) == iminor(inode))
- subsys_dev = device;
- mutex_unlock(&subsys_list_lock);
- if (!subsys_dev)
- return -EINVAL;
- retval = subsystem_get_with_fwname(subsys_dev->desc->name,
- subsys_dev->desc->fw_name);
- if (IS_ERR(retval))
- return PTR_ERR(retval);
- return 0;
- }
- static int subsys_device_close(struct inode *inode, struct file *file)
- {
- struct subsys_device *device, *subsys_dev = 0;
- mutex_lock(&subsys_list_lock);
- list_for_each_entry(device, &subsys_list, list)
- if (MINOR(device->dev_no) == iminor(inode))
- subsys_dev = device;
- mutex_unlock(&subsys_list_lock);
- if (!subsys_dev)
- return -EINVAL;
- subsystem_put(subsys_dev);
- return 0;
- }
- static const struct file_operations subsys_device_fops = {
- .owner = THIS_MODULE,
- .open = subsys_device_open,
- .release = subsys_device_close,
- };
- static void subsys_device_release(struct device *dev)
- {
- struct subsys_device *subsys = to_subsys(dev);
- wakeup_source_trash(&subsys->ssr_wlock);
- mutex_destroy(&subsys->track.lock);
- ida_simple_remove(&subsys_ida, subsys->id);
- kfree(subsys);
- }
- static irqreturn_t subsys_err_ready_intr_handler(int irq, void *subsys)
- {
- struct subsys_device *subsys_dev = subsys;
- dev_info(subsys_dev->desc->dev,
- "Subsystem error monitoring/handling services are up\n");
- if (subsys_dev->desc->is_not_loadable)
- return IRQ_HANDLED;
- complete(&subsys_dev->err_ready);
- return IRQ_HANDLED;
- }
- static int subsys_char_device_add(struct subsys_device *subsys_dev)
- {
- int ret = 0;
- static int major, minor;
- dev_t dev_no;
- mutex_lock(&char_device_lock);
- if (!major) {
- ret = alloc_chrdev_region(&dev_no, 0, 4, "subsys");
- if (ret < 0) {
- pr_err("Failed to alloc subsys_dev region, err %d\n",
- ret);
- goto fail;
- }
- major = MAJOR(dev_no);
- minor = MINOR(dev_no);
- } else
- dev_no = MKDEV(major, minor);
- if (!device_create(char_class, subsys_dev->desc->dev, dev_no,
- NULL, "subsys_%s", subsys_dev->desc->name)) {
- pr_err("Failed to create subsys_%s device\n",
- subsys_dev->desc->name);
- goto fail_unregister_cdev_region;
- }
- cdev_init(&subsys_dev->char_dev, &subsys_device_fops);
- subsys_dev->char_dev.owner = THIS_MODULE;
- ret = cdev_add(&subsys_dev->char_dev, dev_no, 1);
- if (ret < 0)
- goto fail_destroy_device;
- subsys_dev->dev_no = dev_no;
- minor++;
- mutex_unlock(&char_device_lock);
- return 0;
- fail_destroy_device:
- device_destroy(char_class, dev_no);
- fail_unregister_cdev_region:
- unregister_chrdev_region(dev_no, 1);
- fail:
- mutex_unlock(&char_device_lock);
- return ret;
- }
- static void subsys_char_device_remove(struct subsys_device *subsys_dev)
- {
- cdev_del(&subsys_dev->char_dev);
- device_destroy(char_class, subsys_dev->dev_no);
- unregister_chrdev_region(subsys_dev->dev_no, 1);
- }
- static void subsys_remove_restart_order(struct device_node *device)
- {
- struct subsys_soc_restart_order *order;
- int i;
- mutex_lock(&ssr_order_mutex);
- list_for_each_entry(order, &ssr_order_list, list)
- for (i = 0; i < order->count; i++)
- if (order->device_ptrs[i] == device)
- order->subsys_ptrs[i] = NULL;
- mutex_unlock(&ssr_order_mutex);
- }
- static struct subsys_soc_restart_order *ssr_parse_restart_orders(struct
- subsys_desc * desc)
- {
- int i, j, count, num = 0;
- struct subsys_soc_restart_order *order, *tmp;
- struct device *dev = desc->dev;
- struct device_node *ssr_node;
- uint32_t len;
- if (!of_get_property(dev->of_node, "qcom,restart-group", &len))
- return NULL;
- count = len/sizeof(uint32_t);
- order = devm_kzalloc(dev, sizeof(*order), GFP_KERNEL);
- if (!order)
- return ERR_PTR(-ENOMEM);
- order->subsys_ptrs = devm_kzalloc(dev,
- count * sizeof(struct subsys_device *),
- GFP_KERNEL);
- if (!order->subsys_ptrs)
- return ERR_PTR(-ENOMEM);
- order->device_ptrs = devm_kzalloc(dev,
- count * sizeof(struct device_node *),
- GFP_KERNEL);
- if (!order->device_ptrs)
- return ERR_PTR(-ENOMEM);
- for (i = 0; i < count; i++) {
- ssr_node = of_parse_phandle(dev->of_node,
- "qcom,restart-group", i);
- if (!ssr_node)
- return ERR_PTR(-ENXIO);
- of_node_put(ssr_node);
- pr_info("%s device has been added to %s's restart group\n",
- ssr_node->name, desc->name);
- order->device_ptrs[i] = ssr_node;
- }
- /*
- * Check for similar restart groups. If found, return
- * without adding the new group to the ssr_order_list.
- */
- mutex_lock(&ssr_order_mutex);
- list_for_each_entry(tmp, &ssr_order_list, list) {
- for (i = 0; i < count; i++) {
- for (j = 0; j < count; j++) {
- if (order->device_ptrs[j] !=
- tmp->device_ptrs[i])
- continue;
- else
- num++;
- }
- }
- if (num == count && tmp->count == count)
- goto err;
- else if (num) {
- tmp = ERR_PTR(-EINVAL);
- goto err;
- }
- }
- order->count = count;
- mutex_init(&order->track.lock);
- spin_lock_init(&order->track.s_lock);
- INIT_LIST_HEAD(&order->list);
- list_add_tail(&order->list, &ssr_order_list);
- mutex_unlock(&ssr_order_mutex);
- return order;
- err:
- mutex_unlock(&ssr_order_mutex);
- return tmp;
- }
- static int __get_gpio(struct subsys_desc *desc, const char *prop,
- int *gpio)
- {
- struct device_node *dnode = desc->dev->of_node;
- int ret = -ENOENT;
- if (of_find_property(dnode, prop, NULL)) {
- *gpio = of_get_named_gpio(dnode, prop, 0);
- ret = *gpio < 0 ? *gpio : 0;
- }
- return ret;
- }
- static int __get_irq(struct subsys_desc *desc, const char *prop,
- unsigned int *irq, int *gpio)
- {
- int ret, gpiol, irql;
- ret = __get_gpio(desc, prop, &gpiol);
- if (ret)
- return ret;
- irql = gpio_to_irq(gpiol);
- if (irql == -ENOENT)
- irql = -ENXIO;
- if (irql < 0) {
- pr_err("[%s]: Error getting IRQ \"%s\"\n", desc->name,
- prop);
- return irql;
- }
- if (gpio)
- *gpio = gpiol;
- *irq = irql;
- return 0;
- }
- static int subsys_parse_devicetree(struct subsys_desc *desc)
- {
- struct subsys_soc_restart_order *order;
- int ret;
- struct platform_device *pdev = container_of(desc->dev,
- struct platform_device, dev);
- ret = __get_irq(desc, "qcom,gpio-err-fatal", &desc->err_fatal_irq,
- &desc->err_fatal_gpio);
- if (ret && ret != -ENOENT)
- return ret;
- ret = __get_irq(desc, "qcom,gpio-err-ready", &desc->err_ready_irq,
- NULL);
- if (ret && ret != -ENOENT)
- return ret;
- ret = __get_irq(desc, "qcom,gpio-stop-ack", &desc->stop_ack_irq, NULL);
- if (ret && ret != -ENOENT)
- return ret;
- ret = __get_gpio(desc, "qcom,gpio-force-stop", &desc->force_stop_gpio);
- if (ret && ret != -ENOENT)
- return ret;
- ret = __get_gpio(desc, "qcom,gpio-ramdump-disable",
- &desc->ramdump_disable_gpio);
- if (ret && ret != -ENOENT)
- return ret;
- ret = __get_gpio(desc, "qcom,gpio-shutdown-ack",
- &desc->shutdown_ack_gpio);
- if (ret && ret != -ENOENT)
- return ret;
- ret = platform_get_irq(pdev, 0);
- if (ret > 0)
- desc->wdog_bite_irq = ret;
- if (of_property_read_bool(pdev->dev.of_node,
- "qcom,pil-generic-irq-handler")) {
- ret = platform_get_irq(pdev, 0);
- if (ret > 0)
- desc->generic_irq = ret;
- }
- desc->ignore_ssr_failure = of_property_read_bool(pdev->dev.of_node,
- "qcom,ignore-ssr-failure");
- order = ssr_parse_restart_orders(desc);
- if (IS_ERR(order)) {
- pr_err("Could not initialize SSR restart order, err = %ld\n",
- PTR_ERR(order));
- return PTR_ERR(order);
- }
- return 0;
- }
- static int subsys_setup_irqs(struct subsys_device *subsys)
- {
- struct subsys_desc *desc = subsys->desc;
- int ret;
- if (desc->err_fatal_irq && desc->err_fatal_handler) {
- ret = devm_request_irq(desc->dev, desc->err_fatal_irq,
- desc->err_fatal_handler,
- IRQF_TRIGGER_RISING, desc->name, desc);
- if (ret < 0) {
- dev_err(desc->dev, "[%s]: Unable to register error fatal IRQ handler!: %d\n",
- desc->name, ret);
- return ret;
- }
- disable_irq(desc->err_fatal_irq);
- }
- if (desc->stop_ack_irq && desc->stop_ack_handler) {
- ret = devm_request_irq(desc->dev, desc->stop_ack_irq,
- desc->stop_ack_handler,
- IRQF_TRIGGER_RISING, desc->name, desc);
- if (ret < 0) {
- dev_err(desc->dev, "[%s]: Unable to register stop ack handler!: %d\n",
- desc->name, ret);
- return ret;
- }
- disable_irq(desc->stop_ack_irq);
- }
- if (desc->wdog_bite_irq && desc->wdog_bite_handler) {
- ret = devm_request_irq(desc->dev, desc->wdog_bite_irq,
- desc->wdog_bite_handler,
- IRQF_TRIGGER_RISING, desc->name, desc);
- if (ret < 0) {
- dev_err(desc->dev, "[%s]: Unable to register wdog bite handler!: %d\n",
- desc->name, ret);
- return ret;
- }
- disable_irq(desc->wdog_bite_irq);
- }
- if (desc->generic_irq && desc->generic_handler) {
- ret = devm_request_irq(desc->dev, desc->generic_irq,
- desc->generic_handler,
- IRQF_TRIGGER_HIGH, desc->name, desc);
- if (ret < 0) {
- dev_err(desc->dev, "[%s]: Unable to register generic irq handler!: %d\n",
- desc->name, ret);
- return ret;
- }
- disable_irq(desc->generic_irq);
- }
- if (desc->err_ready_irq) {
- ret = devm_request_irq(desc->dev,
- desc->err_ready_irq,
- subsys_err_ready_intr_handler,
- IRQF_TRIGGER_RISING,
- "error_ready_interrupt", subsys);
- if (ret < 0) {
- dev_err(desc->dev,
- "[%s]: Unable to register err ready handler\n",
- desc->name);
- return ret;
- }
- disable_irq(desc->err_ready_irq);
- }
- return 0;
- }
- static void subsys_free_irqs(struct subsys_device *subsys)
- {
- struct subsys_desc *desc = subsys->desc;
- if (desc->err_fatal_irq && desc->err_fatal_handler)
- devm_free_irq(desc->dev, desc->err_fatal_irq, desc);
- if (desc->stop_ack_irq && desc->stop_ack_handler)
- devm_free_irq(desc->dev, desc->stop_ack_irq, desc);
- if (desc->wdog_bite_irq && desc->wdog_bite_handler)
- devm_free_irq(desc->dev, desc->wdog_bite_irq, desc);
- if (desc->err_ready_irq)
- devm_free_irq(desc->dev, desc->err_ready_irq, subsys);
- }
- struct subsys_device *subsys_register(struct subsys_desc *desc)
- {
- struct subsys_device *subsys;
- struct device_node *ofnode = desc->dev->of_node;
- int ret;
- subsys = kzalloc(sizeof(*subsys), GFP_KERNEL);
- if (!subsys)
- return ERR_PTR(-ENOMEM);
- subsys->desc = desc;
- subsys->owner = desc->owner;
- subsys->dev.parent = desc->dev;
- subsys->dev.bus = &subsys_bus_type;
- subsys->dev.release = subsys_device_release;
- subsys->notif_state = -1;
- subsys->desc->sysmon_pid = -1;
- strlcpy(subsys->desc->fw_name, desc->name,
- sizeof(subsys->desc->fw_name));
- subsys->notify = subsys_notif_add_subsys(desc->name);
- snprintf(subsys->wlname, sizeof(subsys->wlname), "ssr(%s)", desc->name);
- wakeup_source_init(&subsys->ssr_wlock, subsys->wlname);
- INIT_WORK(&subsys->work, subsystem_restart_wq_func);
- INIT_WORK(&subsys->device_restart_work, device_restart_work_hdlr);
- spin_lock_init(&subsys->track.s_lock);
- subsys->id = ida_simple_get(&subsys_ida, 0, 0, GFP_KERNEL);
- if (subsys->id < 0) {
- wakeup_source_trash(&subsys->ssr_wlock);
- ret = subsys->id;
- kfree(subsys);
- return ERR_PTR(ret);
- }
- dev_set_name(&subsys->dev, "subsys%d", subsys->id);
- mutex_init(&subsys->track.lock);
- ret = subsys_debugfs_add(subsys);
- if (ret) {
- ida_simple_remove(&subsys_ida, subsys->id);
- wakeup_source_trash(&subsys->ssr_wlock);
- kfree(subsys);
- return ERR_PTR(ret);
- }
- ret = device_register(&subsys->dev);
- if (ret) {
- subsys_debugfs_remove(subsys);
- put_device(&subsys->dev);
- return ERR_PTR(ret);
- }
- ret = subsys_char_device_add(subsys);
- if (ret)
- goto err_register;
- if (ofnode) {
- ret = subsys_parse_devicetree(desc);
- if (ret)
- goto err_register;
- subsys->restart_order = update_restart_order(subsys);
- ret = subsys_setup_irqs(subsys);
- if (ret < 0)
- goto err_setup_irqs;
- if (of_property_read_u32(ofnode, "qcom,ssctl-instance-id",
- &desc->ssctl_instance_id))
- pr_debug("Reading instance-id for %s failed\n",
- desc->name);
- if (of_property_read_u32(ofnode, "qcom,sysmon-id",
- &subsys->desc->sysmon_pid))
- pr_debug("Reading sysmon-id for %s failed\n",
- desc->name);
- subsys->desc->edge = of_get_property(ofnode, "qcom,edge",
- NULL);
- if (!subsys->desc->edge)
- pr_debug("Reading qcom,edge for %s failed\n",
- desc->name);
- }
- ret = sysmon_notifier_register(desc);
- if (ret < 0)
- goto err_sysmon_notifier;
- if (subsys->desc->edge) {
- ret = sysmon_glink_register(desc);
- if (ret < 0)
- goto err_sysmon_glink_register;
- }
- mutex_lock(&subsys_list_lock);
- INIT_LIST_HEAD(&subsys->list);
- list_add_tail(&subsys->list, &subsys_list);
- mutex_unlock(&subsys_list_lock);
- return subsys;
- err_sysmon_glink_register:
- sysmon_notifier_unregister(subsys->desc);
- err_sysmon_notifier:
- if (ofnode)
- subsys_free_irqs(subsys);
- err_setup_irqs:
- if (ofnode)
- subsys_remove_restart_order(ofnode);
- err_register:
- subsys_debugfs_remove(subsys);
- device_unregister(&subsys->dev);
- return ERR_PTR(ret);
- }
- EXPORT_SYMBOL(subsys_register);
- void subsys_unregister(struct subsys_device *subsys)
- {
- struct subsys_device *subsys_dev, *tmp;
- struct device_node *device = subsys->desc->dev->of_node;
- if (IS_ERR_OR_NULL(subsys))
- return;
- if (get_device(&subsys->dev)) {
- mutex_lock(&subsys_list_lock);
- list_for_each_entry_safe(subsys_dev, tmp, &subsys_list, list)
- if (subsys_dev == subsys)
- list_del(&subsys->list);
- mutex_unlock(&subsys_list_lock);
- if (device) {
- subsys_free_irqs(subsys);
- subsys_remove_restart_order(device);
- }
- mutex_lock(&subsys->track.lock);
- WARN_ON(subsys->count);
- device_unregister(&subsys->dev);
- mutex_unlock(&subsys->track.lock);
- subsys_debugfs_remove(subsys);
- subsys_char_device_remove(subsys);
- sysmon_notifier_unregister(subsys->desc);
- if (subsys->desc->edge)
- sysmon_glink_unregister(subsys->desc);
- put_device(&subsys->dev);
- }
- }
- EXPORT_SYMBOL(subsys_unregister);
- static int subsys_panic(struct device *dev, void *data)
- {
- struct subsys_device *subsys = to_subsys(dev);
- if (subsys->desc->crash_shutdown)
- subsys->desc->crash_shutdown(subsys->desc);
- return 0;
- }
- static int ssr_panic_handler(struct notifier_block *this,
- unsigned long event, void *ptr)
- {
- bus_for_each_dev(&subsys_bus_type, NULL, NULL, subsys_panic);
- return NOTIFY_DONE;
- }
- static struct notifier_block panic_nb = {
- .notifier_call = ssr_panic_handler,
- };
- static int __init subsys_restart_init(void)
- {
- int ret;
- ssr_wq = alloc_workqueue("ssr_wq", WQ_CPU_INTENSIVE, 0);
- BUG_ON(!ssr_wq);
- ret = bus_register(&subsys_bus_type);
- if (ret)
- goto err_bus;
- ret = subsys_debugfs_init();
- if (ret)
- goto err_debugfs;
- char_class = class_create(THIS_MODULE, "subsys");
- if (IS_ERR(char_class)) {
- ret = -ENOMEM;
- pr_err("Failed to create subsys_dev class\n");
- goto err_class;
- }
- ret = atomic_notifier_chain_register(&panic_notifier_list,
- &panic_nb);
- if (ret)
- goto err_soc;
- return 0;
- err_soc:
- class_destroy(char_class);
- err_class:
- subsys_debugfs_exit();
- err_debugfs:
- bus_unregister(&subsys_bus_type);
- err_bus:
- destroy_workqueue(ssr_wq);
- return ret;
- }
- arch_initcall(subsys_restart_init);
- MODULE_DESCRIPTION("Subsystem Restart Driver");
- MODULE_LICENSE("GPL v2");
|