1*4582Scth /*
2*4582Scth * CDDL HEADER START
3*4582Scth *
4*4582Scth * The contents of this file are subject to the terms of the
5*4582Scth * Common Development and Distribution License (the "License").
6*4582Scth * You may not use this file except in compliance with the License.
7*4582Scth *
8*4582Scth * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9*4582Scth * or http://www.opensolaris.org/os/licensing.
10*4582Scth * See the License for the specific language governing permissions
11*4582Scth * and limitations under the License.
12*4582Scth *
13*4582Scth * When distributing Covered Code, include this CDDL HEADER in each
14*4582Scth * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15*4582Scth * If applicable, add the following below this CDDL HEADER, with the
16*4582Scth * fields enclosed by brackets "[]" replaced with your own identifying
17*4582Scth * information: Portions Copyright [yyyy] [name of copyright owner]
18*4582Scth *
19*4582Scth * CDDL HEADER END
20*4582Scth */
21*4582Scth
22*4582Scth /*
23*4582Scth * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
24*4582Scth * Use is subject to license terms.
25*4582Scth */
26*4582Scth
27*4582Scth #pragma ident "%Z%%M% %I% %E% SMI"
28*4582Scth
29*4582Scth #include <string.h>
30*4582Scth #include <inttypes.h>
31*4582Scth #include <atomic.h>
32*4582Scth #include <fm/fmd_api.h>
33*4582Scth #include <sys/fm/protocol.h>
34*4582Scth
35*4582Scth #include "disk_monitor.h"
36*4582Scth #include "schg_mgr.h"
37*4582Scth #include "hotplug_mgr.h"
38*4582Scth #include "topo_gather.h"
39*4582Scth #include "dm_platform.h"
40*4582Scth
41*4582Scth /* State-change event processing thread data */
42*4582Scth static pthread_t g_schg_tid;
43*4582Scth static thread_state_t g_schgt_state = TS_NOT_RUNNING;
44*4582Scth static pthread_mutex_t g_schgt_state_mutex = PTHREAD_MUTEX_INITIALIZER;
45*4582Scth static pthread_cond_t g_schgt_state_cvar = PTHREAD_COND_INITIALIZER;
46*4582Scth static pthread_mutex_t g_schgt_add_mutex = PTHREAD_MUTEX_INITIALIZER;
47*4582Scth static qu_t *g_schg_queue = NULL;
48*4582Scth
49*4582Scth static void dm_state_change_nolock(diskmon_t *diskp, hotplug_state_t newstate);
50*4582Scth
51*4582Scth /*
52*4582Scth * Each disk state change is described by an instance of the following
53*4582Scth * structure (which includes the disk object and the new state)
54*4582Scth */
55*4582Scth typedef struct disk_statechg {
56*4582Scth diskmon_t *diskp;
57*4582Scth hotplug_state_t newstate;
58*4582Scth } disk_statechg_t;
59*4582Scth
60*4582Scth static disk_statechg_t *
new_statechange(diskmon_t * diskp,hotplug_state_t state)61*4582Scth new_statechange(diskmon_t *diskp, hotplug_state_t state)
62*4582Scth {
63*4582Scth disk_statechg_t *dscp =
64*4582Scth (disk_statechg_t *)dmalloc(sizeof (disk_statechg_t));
65*4582Scth
66*4582Scth /*
67*4582Scth * The states are additive -- we don't need to preserve
68*4582Scth * the current faulted state in the newstate:
69*4582Scth */
70*4582Scth dscp->diskp = diskp;
71*4582Scth dscp->newstate = state;
72*4582Scth
73*4582Scth return (dscp);
74*4582Scth }
75*4582Scth
76*4582Scth static void
free_statechange(void * dscp)77*4582Scth free_statechange(void *dscp)
78*4582Scth {
79*4582Scth dfree(dscp, sizeof (disk_statechg_t));
80*4582Scth }
81*4582Scth
82*4582Scth static void
add_to_statechange_queue(diskmon_t * diskp,hotplug_state_t newstate)83*4582Scth add_to_statechange_queue(diskmon_t *diskp, hotplug_state_t newstate)
84*4582Scth {
85*4582Scth queue_add(g_schg_queue, new_statechange(diskp, newstate));
86*4582Scth }
87*4582Scth
88*4582Scth static const char *
lookup_action_string(indicator_t * ind_listp,ind_state_t state,char * name)89*4582Scth lookup_action_string(indicator_t *ind_listp, ind_state_t state, char *name)
90*4582Scth {
91*4582Scth const char *str = NULL;
92*4582Scth
93*4582Scth while (ind_listp != NULL) {
94*4582Scth
95*4582Scth if (state == ind_listp->ind_state &&
96*4582Scth strcasecmp(ind_listp->ind_name, name) == 0) {
97*4582Scth
98*4582Scth str = ind_listp->ind_instr_spec;
99*4582Scth break;
100*4582Scth }
101*4582Scth
102*4582Scth ind_listp = ind_listp->next;
103*4582Scth }
104*4582Scth
105*4582Scth return (str);
106*4582Scth }
107*4582Scth
108*4582Scth void
dm_fault_indicator_set(diskmon_t * diskp,ind_state_t istate)109*4582Scth dm_fault_indicator_set(diskmon_t *diskp, ind_state_t istate)
110*4582Scth {
111*4582Scth const char *astring;
112*4582Scth
113*4582Scth dm_assert(pthread_mutex_lock(&diskp->fault_indicator_mutex) == 0);
114*4582Scth
115*4582Scth /*
116*4582Scth * No need to execute redundant indicator actions
117*4582Scth */
118*4582Scth if (istate == INDICATOR_UNKNOWN ||
119*4582Scth diskp->fault_indicator_state == istate) {
120*4582Scth dm_assert(pthread_mutex_unlock(&diskp->fault_indicator_mutex)
121*4582Scth == 0);
122*4582Scth return;
123*4582Scth }
124*4582Scth
125*4582Scth astring = lookup_action_string(diskp->ind_list, istate,
126*4582Scth INDICATOR_FAULT_IDENTIFIER);
127*4582Scth
128*4582Scth if (astring != NULL) {
129*4582Scth log_msg(MM_SCHGMGR, "Executing action `%s'\n", astring);
130*4582Scth
131*4582Scth if (dm_platform_indicator_execute(astring) != 0) {
132*4582Scth log_warn("[Disk in %s] Action `%s' did not complete "
133*4582Scth "successfully.\n",
134*4582Scth diskp->location,
135*4582Scth astring);
136*4582Scth } else {
137*4582Scth
138*4582Scth diskp->fault_indicator_state = istate;
139*4582Scth
140*4582Scth log_msg(MM_SCHGMGR, "Action `%s' executed "
141*4582Scth "successfully\n", astring);
142*4582Scth }
143*4582Scth }
144*4582Scth
145*4582Scth dm_assert(pthread_mutex_unlock(&diskp->fault_indicator_mutex) == 0);
146*4582Scth }
147*4582Scth
148*4582Scth static void
schg_execute_state_change_action(diskmon_t * diskp,hotplug_state_t oldstate,hotplug_state_t newstate)149*4582Scth schg_execute_state_change_action(diskmon_t *diskp, hotplug_state_t oldstate,
150*4582Scth hotplug_state_t newstate)
151*4582Scth {
152*4582Scth indrule_t *rulelist;
153*4582Scth ind_action_t *actions;
154*4582Scth const char *astring;
155*4582Scth
156*4582Scth log_msg(MM_SCHGMGR, "[Disk in %s] State change action: %s -> %s\n",
157*4582Scth diskp->location,
158*4582Scth hotplug_state_string(oldstate),
159*4582Scth hotplug_state_string(newstate));
160*4582Scth
161*4582Scth /*
162*4582Scth * Find the list of actions that correspond to this state change.
163*4582Scth * If the old state is UNKNOWN, then we'll match to first action
164*4582Scth * whose transition state is the new state.
165*4582Scth */
166*4582Scth rulelist = diskp->indrule_list;
167*4582Scth
168*4582Scth while (rulelist != NULL) {
169*4582Scth
170*4582Scth if ((oldstate == HPS_UNKNOWN ||
171*4582Scth rulelist->strans.begin == oldstate) &&
172*4582Scth rulelist->strans.end == newstate)
173*4582Scth break;
174*4582Scth
175*4582Scth rulelist = rulelist->next;
176*4582Scth }
177*4582Scth
178*4582Scth if (rulelist != NULL) {
179*4582Scth /* Now we have a set of actions to perform: */
180*4582Scth actions = rulelist->action_list;
181*4582Scth
182*4582Scth while (actions != NULL) {
183*4582Scth
184*4582Scth astring = lookup_action_string(diskp->ind_list,
185*4582Scth actions->ind_state, actions->ind_name);
186*4582Scth
187*4582Scth dm_assert(astring != NULL);
188*4582Scth
189*4582Scth log_msg(MM_SCHGMGR, "Executing action `%s'\n", astring);
190*4582Scth
191*4582Scth if (dm_platform_indicator_execute(astring) != 0) {
192*4582Scth log_warn("[Disk in %s][State transition from "
193*4582Scth "%s to %s] Action `%s' did not complete "
194*4582Scth "successfully.\n",
195*4582Scth diskp->location,
196*4582Scth hotplug_state_string(oldstate),
197*4582Scth hotplug_state_string(newstate),
198*4582Scth astring);
199*4582Scth
200*4582Scth } else
201*4582Scth log_msg(MM_SCHGMGR,
202*4582Scth "Action `%s' executed successfully\n",
203*4582Scth astring);
204*4582Scth
205*4582Scth actions = actions->next;
206*4582Scth }
207*4582Scth }
208*4582Scth
209*4582Scth }
210*4582Scth
211*4582Scth static void
schg_send_fru_update(diskmon_t * diskp,dm_fru_t * frup)212*4582Scth schg_send_fru_update(diskmon_t *diskp, dm_fru_t *frup)
213*4582Scth {
214*4582Scth const char *action = dm_prop_lookup(diskp->props, DISK_PROP_FRUACTION);
215*4582Scth
216*4582Scth if (action == NULL) {
217*4582Scth log_msg(MM_SCHGMGR|MM_NOTE, "No FRU update action for disk "
218*4582Scth "in %s\n", diskp->location);
219*4582Scth return;
220*4582Scth }
221*4582Scth
222*4582Scth if (dm_platform_update_fru(action, frup) != 0) {
223*4582Scth log_warn("Error updating FRU information for disk in %s.\n",
224*4582Scth diskp->location);
225*4582Scth }
226*4582Scth }
227*4582Scth
228*4582Scth static void
schg_update_fru_info(diskmon_t * diskp)229*4582Scth schg_update_fru_info(diskmon_t *diskp)
230*4582Scth {
231*4582Scth if (diskp->initial_configuration ||
232*4582Scth update_configuration_from_topo(g_fm_hdl, diskp) == TOPO_SUCCESS) {
233*4582Scth diskp->initial_configuration = B_FALSE;
234*4582Scth dm_assert(pthread_mutex_lock(&diskp->fru_mutex) == 0);
235*4582Scth if (diskp->frup != NULL)
236*4582Scth schg_send_fru_update(diskp, diskp->frup);
237*4582Scth else
238*4582Scth log_warn("frup unexpectedly went away: not updating "
239*4582Scth "FRU information for disk %s!\n", diskp->location);
240*4582Scth dm_assert(pthread_mutex_unlock(&diskp->fru_mutex) == 0);
241*4582Scth } else {
242*4582Scth log_warn_e("Error retrieving FRU information "
243*4582Scth "for disk in %s", diskp->location);
244*4582Scth }
245*4582Scth }
246*4582Scth
247*4582Scth void
block_state_change_events(void)248*4582Scth block_state_change_events(void)
249*4582Scth {
250*4582Scth dm_assert(pthread_mutex_lock(&g_schgt_add_mutex) == 0);
251*4582Scth }
252*4582Scth
253*4582Scth void
unblock_state_change_events(void)254*4582Scth unblock_state_change_events(void)
255*4582Scth {
256*4582Scth dm_assert(pthread_mutex_unlock(&g_schgt_add_mutex) == 0);
257*4582Scth }
258*4582Scth
259*4582Scth static void
disk_state_change_first_time(diskmon_t * diskp)260*4582Scth disk_state_change_first_time(diskmon_t *diskp)
261*4582Scth {
262*4582Scth hotplug_state_t firststate;
263*4582Scth
264*4582Scth /*
265*4582Scth * Grab the current state of the attachment point to initialize the
266*4582Scth * initial disk state. Create a disk state change with this new
267*4582Scth * state so it will be processed in the loop below. If we can't get
268*4582Scth * the initial state for some reason, then we'll just end up doing it
269*4582Scth * later when we get a state change from the hotplug monitor or the
270*4582Scth * fault monitor.
271*4582Scth */
272*4582Scth firststate = disk_ap_state_to_hotplug_state(diskp);
273*4582Scth if (firststate != HPS_UNKNOWN)
274*4582Scth dm_state_change_nolock(diskp, firststate);
275*4582Scth
276*4582Scth /*
277*4582Scth * The fault indicators will be updated when faults are replayed
278*4582Scth * based on the state of the disk as faulty in the fmd resource cache.
279*4582Scth * A FAULTED state change will come from the _recv function when the
280*4582Scth * fault component event is replayed.
281*4582Scth */
282*4582Scth }
283*4582Scth
284*4582Scth static void
disk_state_change_thread(void * vdisklistp)285*4582Scth disk_state_change_thread(void *vdisklistp)
286*4582Scth {
287*4582Scth diskmon_t *disklistp = (diskmon_t *)vdisklistp;
288*4582Scth diskmon_t *diskp;
289*4582Scth disk_statechg_t *dscp;
290*4582Scth hotplug_state_t nextstate;
291*4582Scth const char *pth;
292*4582Scth
293*4582Scth /*
294*4582Scth * Perform startup activities to initialize the state of the
295*4582Scth * indicators for each disk.
296*4582Scth */
297*4582Scth diskp = disklistp;
298*4582Scth while (diskp != NULL) {
299*4582Scth disk_state_change_first_time(diskp);
300*4582Scth diskp = diskp->next;
301*4582Scth }
302*4582Scth
303*4582Scth unblock_state_change_events();
304*4582Scth
305*4582Scth dm_assert(pthread_mutex_lock(&g_schgt_state_mutex) == 0);
306*4582Scth if (g_schgt_state != TS_EXIT_REQUESTED) {
307*4582Scth g_schgt_state = TS_RUNNING;
308*4582Scth dm_assert(pthread_cond_broadcast(&g_schgt_state_cvar) == 0);
309*4582Scth }
310*4582Scth dm_assert(pthread_mutex_unlock(&g_schgt_state_mutex) == 0);
311*4582Scth
312*4582Scth while (g_schgt_state != TS_EXIT_REQUESTED) {
313*4582Scth
314*4582Scth if ((dscp = (disk_statechg_t *)queue_remove(g_schg_queue))
315*4582Scth == NULL) {
316*4582Scth dm_assert(g_schgt_state == TS_EXIT_REQUESTED);
317*4582Scth continue;
318*4582Scth }
319*4582Scth
320*4582Scth diskp = dscp->diskp;
321*4582Scth
322*4582Scth /*
323*4582Scth * If the new state is the faulted state, add that state to
324*4582Scth * the disk's current state.
325*4582Scth */
326*4582Scth if (dscp->newstate == HPS_FAULTED) {
327*4582Scth
328*4582Scth /*
329*4582Scth * If the disk wasn't previously in the faulted state,
330*4582Scth * execute the generic fault action. Even if we're
331*4582Scth * in the faulted state, accept additional faults.
332*4582Scth */
333*4582Scth nextstate = DISK_STATE(diskp->state) | HPS_FAULTED;
334*4582Scth
335*4582Scth } else if (dscp->newstate == HPS_REPAIRED) {
336*4582Scth nextstate = DISK_STATE(diskp->state);
337*4582Scth
338*4582Scth } else if (dscp->newstate == HPS_ABSENT) {
339*4582Scth /*
340*4582Scth * If the new state is ABSENT, forget any faults
341*4582Scth */
342*4582Scth
343*4582Scth nextstate = HPS_ABSENT;
344*4582Scth } else
345*4582Scth nextstate = dscp->newstate | DISK_FAULTED(diskp->state);
346*4582Scth
347*4582Scth /*
348*4582Scth * When a new disk is inserted and reaches the CONFIGURED state,
349*4582Scth * the following actions must be done in the following order:
350*4582Scth *
351*4582Scth * (1) Execute the configuration-specified action on the
352*4582Scth * state change.
353*4582Scth * (2) Retreive the FRU information from the disk and execute
354*4582Scth * the FRU-update action specified,
355*4582Scth * (3) Initialize the fault monitor state associated with
356*4582Scth * the new drive.
357*4582Scth *
358*4582Scth * Once the disk is no longer "new" (a disk is "new" when it
359*4582Scth * has not yet reached the CONFIGURED state), subsequent
360*4582Scth * transitions away and back to CONFIGURED (as long as the
361*4582Scth * disk is not physically removed) will result in the
362*4582Scth * execution of the predefined action ONLY.
363*4582Scth *
364*4582Scth */
365*4582Scth
366*4582Scth if (dscp->newstate != HPS_FAULTED &&
367*4582Scth DISK_STATE(nextstate) != HPS_UNKNOWN &&
368*4582Scth dscp->newstate != HPS_REPAIRED) {
369*4582Scth
370*4582Scth schg_execute_state_change_action(diskp,
371*4582Scth DISK_STATE(diskp->state), DISK_STATE(nextstate));
372*4582Scth }
373*4582Scth
374*4582Scth if (!diskp->configured_yet &&
375*4582Scth DISK_STATE(nextstate) == HPS_CONFIGURED) {
376*4582Scth
377*4582Scth schg_update_fru_info(diskp);
378*4582Scth
379*4582Scth /*
380*4582Scth * If this state transition is lagging the true
381*4582Scth * state of the system (e.g. if the true state of
382*4582Scth * the disk is UNCONFIGURED, there's another
383*4582Scth * state change somewhere later in the queue), then
384*4582Scth * it's possible for the disk path property to not
385*4582Scth * exist.
386*4582Scth */
387*4582Scth if (dm_prop_lookup(diskp->props,
388*4582Scth DISK_PROP_DEVPATH) == NULL) {
389*4582Scth
390*4582Scth log_msg(MM_SCHGMGR,
391*4582Scth "Processed stale state change "
392*4582Scth "for disk %s\n", diskp->location);
393*4582Scth
394*4582Scth } else {
395*4582Scth diskp->configured_yet = B_TRUE;
396*4582Scth }
397*4582Scth
398*4582Scth }
399*4582Scth
400*4582Scth dm_assert(pthread_mutex_lock(&diskp->manager_mutex) == 0);
401*4582Scth
402*4582Scth /*
403*4582Scth * Make the new state visible to all observers
404*4582Scth */
405*4582Scth diskp->state = nextstate;
406*4582Scth
407*4582Scth /*
408*4582Scth * Now, update the diskmon if the disk is now absent -- it's
409*4582Scth * essential to do this after the state is set (above) so that
410*4582Scth * state observers in other threads don't try to access the
411*4582Scth * data structures that we're freeing here.
412*4582Scth */
413*4582Scth
414*4582Scth if (diskp->configured_yet &&
415*4582Scth DISK_STATE(nextstate) == HPS_ABSENT) {
416*4582Scth /*
417*4582Scth * When the disk is removed, the fault monitor state is
418*4582Scth * useless, so discard it.
419*4582Scth */
420*4582Scth dm_assert(DISK_STATE(nextstate) != HPS_CONFIGURED);
421*4582Scth
422*4582Scth diskp->configured_yet = B_FALSE;
423*4582Scth
424*4582Scth }
425*4582Scth dm_assert(pthread_mutex_unlock(&diskp->manager_mutex) == 0);
426*4582Scth
427*4582Scth pth = dm_prop_lookup(diskp->props, DISK_PROP_DEVPATH);
428*4582Scth
429*4582Scth log_msg(MM_SCHGMGR,
430*4582Scth "[State change #%d][%s]: Disk path = %s\n",
431*4582Scth diskp->state_change_count,
432*4582Scth diskp->location, pth == NULL ? "Unknown" : pth);
433*4582Scth
434*4582Scth log_msg(MM_SCHGMGR,
435*4582Scth "[State change #%d][%s]: New state = %s%s\n",
436*4582Scth diskp->state_change_count, diskp->location,
437*4582Scth hotplug_state_string(diskp->state),
438*4582Scth DISK_FAULTED(diskp->state) ? "+FAULTED" : "");
439*4582Scth
440*4582Scth atomic_inc_uint(&diskp->state_change_count);
441*4582Scth
442*4582Scth /* The caller is responsible for freeing the state change: */
443*4582Scth free_statechange(dscp);
444*4582Scth }
445*4582Scth dm_assert(pthread_mutex_lock(&g_schgt_state_mutex) == 0);
446*4582Scth g_schgt_state = TS_EXITED;
447*4582Scth dm_assert(pthread_cond_broadcast(&g_schgt_state_cvar) == 0);
448*4582Scth dm_assert(pthread_mutex_unlock(&g_schgt_state_mutex) == 0);
449*4582Scth
450*4582Scth log_msg(MM_SCHGMGR, "State change thread exiting...\n");
451*4582Scth }
452*4582Scth
453*4582Scth static void
dm_state_change_nolock(diskmon_t * diskp,hotplug_state_t newstate)454*4582Scth dm_state_change_nolock(diskmon_t *diskp, hotplug_state_t newstate)
455*4582Scth {
456*4582Scth /* Enqueue a new state change for the state-change thread */
457*4582Scth add_to_statechange_queue(diskp, newstate);
458*4582Scth }
459*4582Scth
460*4582Scth void
dm_state_change(diskmon_t * diskp,hotplug_state_t newstate)461*4582Scth dm_state_change(diskmon_t *diskp, hotplug_state_t newstate)
462*4582Scth {
463*4582Scth dm_assert(pthread_mutex_lock(&g_schgt_add_mutex) == 0);
464*4582Scth dm_state_change_nolock(diskp, newstate);
465*4582Scth dm_assert(pthread_mutex_unlock(&g_schgt_add_mutex) == 0);
466*4582Scth }
467*4582Scth
468*4582Scth int
init_state_change_manager(cfgdata_t * cfgdatap)469*4582Scth init_state_change_manager(cfgdata_t *cfgdatap)
470*4582Scth {
471*4582Scth /* new_queue() is guaranteed to succeed */
472*4582Scth g_schg_queue = new_queue(B_TRUE, dmalloc, dfree, free_statechange);
473*4582Scth
474*4582Scth dm_assert(pthread_mutex_lock(&g_schgt_state_mutex) == 0);
475*4582Scth g_schg_tid = fmd_thr_create(g_fm_hdl, disk_state_change_thread,
476*4582Scth cfgdatap->disk_list);
477*4582Scth
478*4582Scth /*
479*4582Scth * Now, wait for the thread to enter the TS_RUNNING state. This
480*4582Scth * is important because we want the state-change thread to pull the
481*4582Scth * initial state of the disks on startup (without the wait, we could
482*4582Scth * have the hotplug event handler race and deliver a state change
483*4582Scth * before the state-change thread initialized the initial disk state).
484*4582Scth */
485*4582Scth
486*4582Scth while (g_schgt_state != TS_RUNNING) {
487*4582Scth (void) pthread_cond_wait(&g_schgt_state_cvar,
488*4582Scth &g_schgt_state_mutex);
489*4582Scth }
490*4582Scth
491*4582Scth dm_assert(pthread_mutex_unlock(&g_schgt_state_mutex) == 0);
492*4582Scth
493*4582Scth return (0);
494*4582Scth }
495*4582Scth
496*4582Scth /*ARGSUSED*/
497*4582Scth void
cleanup_state_change_manager(cfgdata_t * cfgdatap)498*4582Scth cleanup_state_change_manager(cfgdata_t *cfgdatap)
499*4582Scth {
500*4582Scth if (g_schgt_state != TS_RUNNING)
501*4582Scth return;
502*4582Scth
503*4582Scth g_schgt_state = TS_EXIT_REQUESTED;
504*4582Scth queue_add(g_schg_queue, NULL);
505*4582Scth dm_assert(pthread_mutex_lock(&g_schgt_state_mutex) == 0);
506*4582Scth while (g_schgt_state != TS_EXITED)
507*4582Scth dm_assert(pthread_cond_wait(&g_schgt_state_cvar,
508*4582Scth &g_schgt_state_mutex) == 0);
509*4582Scth dm_assert(pthread_mutex_unlock(&g_schgt_state_mutex) == 0);
510*4582Scth (void) pthread_join(g_schg_tid, NULL);
511*4582Scth fmd_thr_destroy(g_fm_hdl, g_schg_tid);
512*4582Scth queue_free(&g_schg_queue);
513*4582Scth g_schgt_state = TS_NOT_RUNNING;
514*4582Scth }
515