osmo_ss7/src/mtp2_lsc.erl

431 lines
14 KiB
Erlang

% MTP2 Link State Control according to Q.703 Figure 3 / Figure 8
% (C) 2011-2012 by Harald Welte <laforge@gnumonks.org>
%
% All Rights Reserved
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU Affero General Public License as
% published by the Free Software Foundation; either version 3 of the
% License, or (at your option) any later version.
%
% 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.
%
% You should have received a copy of the GNU Affero General Public License
% along with this program. If not, see <http://www.gnu.org/licenses/>.
%
% Additional Permission under GNU AGPL version 3 section 7:
%
% If you modify this Program, or any covered work, by linking or
% combining it with runtime libraries of Erlang/OTP as released by
% Ericsson on http://www.erlang.org (or a modified version of these
% libraries), containing parts covered by the terms of the Erlang Public
% License (http://www.erlang.org/EPLICENSE), the licensors of this
% Program grant you additional permission to convey the resulting work
% without the need to license the runtime libraries of Erlang/OTP under
% the GNU Affero General Public License. Corresponding Source for a
% non-source form of such a combination shall include the source code
% for the parts of the runtime libraries of Erlang/OTP used as well as
% that of the covered work.
-module(mtp2_lsc).
-author('Harald Welte <laforge@gnumonks.org>').
-behaviour(gen_fsm).
% gen_fsm exports
-export([init/1, terminate/3, code_change/4, handle_event/3, handle_info/3]).
% individual FSM states
-export([power_off/2, out_of_service/2, initial_alignment/2,
aligned_not_ready/2, aligned_ready/2, in_service/2,
processor_outage/2]).
% sync event handlers
-export([power_off/3]).
-record(lsc_state, {
t1_timeout,
t1,
iac_pid,
aerm_pid,
l3_pid,
poc_pid,
txc_pid,
rc_pid,
local_proc_out,
proc_out,
emergency
}).
-define(M2PA_T1_DEF, 300000).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% gen_fsm callbacks
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init([Aerm, Rc, Txc, L3, Poc]) ->
{ok, Iac} = gen_fsm:start_link(mtp2_iac, [self(), Aerm, Txc], [{debug, [trace]}]),
LscState = #lsc_state{t1_timeout = ?M2PA_T1_DEF,
iac_pid = Iac,
aerm_pid = Aerm,
l3_pid = L3,
poc_pid = L3,
txc_pid = Txc,
rc_pid = Rc,
local_proc_out = 0,
proc_out = 0,
emergency = 0},
{ok, power_off, LscState}.
terminate(Reason, State, _LoopDat) ->
io:format("Terminating ~p in State ~p (Reason: ~p)~n",
[?MODULE, State, Reason]),
ok.
code_change(_OldVsn, StateName, LoopDat, _Extra) ->
{ok, StateName, LoopDat}.
handle_event(Event, State, LoopDat) ->
io:format("Unknown Event ~p in state ~p~n", [Event, State]),
{next_state, State, LoopDat}.
handle_info(Info, State, LoopDat) ->
io:format("Unknown Info ~p in state ~p~n", [Info, State]),
{next_state, State, LoopDat}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% STATE: power_off
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
power_off(power_on, LoopDat) ->
% Power On from MGMT
send_to(txc, start, LoopDat),
send_to(txc, si_os, LoopDat),
send_to(aerm, set_ti_to_tin, LoopDat),
% Cancel local processor outage, cancel emergency
{next_state, out_of_service, LoopDat#lsc_state{local_proc_out=0, emergency=0}}.
power_off(get_iac_pid, From, LoopDat) ->
Iac = LoopDat#lsc_state.iac_pid,
{reply, {ok, Iac}, power_off, LoopDat}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% STATE: out_of_service
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
out_of_service(start, LoopDat) ->
% Start from L3
send_to(rc, start, LoopDat),
send_to(txc, start, LoopDat),
case LoopDat#lsc_state.emergency of
1 ->
send_to(iac, emergency, LoopDat);
_ ->
ok
end,
send_to(iac, start, LoopDat),
{next_state, initial_alignment, LoopDat};
out_of_service(retrieve_bsnt, LoopDat) ->
send_to(rc, retrieve_bsnt, LoopDat),
{next_state, out_of_service, LoopDat};
out_of_service(retrieval_request_and_fsnc, LoopDat) ->
send_to(txc, retrieval_request_and_fsnc, LoopDat),
{next_state, out_of_service, LoopDat};
out_of_service(emergency, LoopDat) ->
{next_state, out_of_service, LoopDat#lsc_state{emergency=1}};
out_of_service(emergency_ceases, LoopDat) ->
{next_state, out_of_service, LoopDat#lsc_state{emergency=0}};
out_of_service(What, LoopDat) when What == local_processor_outage;
What == level3_failure ->
{next_state, out_of_service, LoopDat#lsc_state{local_proc_out=1}};
out_of_service(si_os, LoopDat) ->
% this transition is not specified in Q.703, but it makes
% quite a bit of sense. yate M2PA requires it, too.
{next_state, out_of_service, LoopDat};
out_of_service(local_processor_recovered, LoopDat) ->
{next_state, out_of_service, LoopDat#lsc_state{local_proc_out=0}}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% STATE: initial_alignment
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
initial_alignment(What, LoopDat) when What == local_processor_outage;
What == level3_failure ->
{next_state, initial_alignment, LoopDat#lsc_state{local_proc_out=1}};
initial_alignment(local_processor_recovered, LoopDat) ->
{next_state, initial_alignment, LoopDat#lsc_state{local_proc_out=0}};
initial_alignment(emergency, LoopDat) ->
send_to(iac, emergency, LoopDat),
{next_state, initial_alignment, LoopDat#lsc_state{emergency=1}};
initial_alignment(alignment_complete, LoopDat) ->
send_to(suerm, start, LoopDat),
{ok, T1} = timer:apply_after(LoopDat#lsc_state.t1_timeout,
gen_fsm, send_event,
[self(), {timer_expired, t1}]),
case LoopDat#lsc_state.local_proc_out of
1 ->
send_to(poc, local_processor_outage, LoopDat),
send_to(txc, si_po, LoopDat),
send_to(rc, reject_msu_fisu, LoopDat),
NextState = aligned_not_ready;
_ ->
send_to(txc, fisu, LoopDat),
send_to(rc, accept_msu_fisu, LoopDat),
NextState = aligned_ready
end,
{next_state, NextState, LoopDat#lsc_state{t1=T1}};
initial_alignment(stop, LoopDat) ->
send_to(iac, stop, LoopDat),
send_to(rc, stop, LoopDat),
send_to(txc, si_os, LoopDat),
{next_state, out_of_service, LoopDat#lsc_state{local_proc_out=0, emergency=0}};
initial_alignment(link_failure, LoopDat) ->
send_to(l3, out_of_service, LoopDat),
send_to(iac, stop, LoopDat),
send_to(rc, stop, LoopDat),
send_to(txc, si_os, LoopDat),
{next_state, out_of_service, LoopDat#lsc_state{local_proc_out=0, emergency=0}};
initial_alignment(alignment_not_possible, LoopDat) ->
send_to(rc, stop, LoopDat),
send_to(txc, si_os, LoopDat),
{next_state, out_of_service, LoopDat#lsc_state{local_proc_out=0, emergency=0}};
% forward into IAC sub-state-machine
initial_alignment(What, LoopDat) when
What == si_n; What == si_e; What == si_o; What == si_os;
What == fisu_msu_received ->
Iac = LoopDat#lsc_state.iac_pid,
gen_fsm:send_event(Iac, What),
{next_state, initial_alignment, LoopDat}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% STATE: aligned_ready
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
aligned_ready(SioOrSios, LoopDat) when SioOrSios == si_o;
SioOrSios == si_os;
SioOrSios == link_failure ->
timer:cancel(LoopDat#lsc_state.t1),
send_to(l3, out_of_service, LoopDat),
send_to(rc, stop, LoopDat),
send_to(suerm, stop, LoopDat),
send_to(txc, si_os, LoopDat),
{next_state, out_of_service, LoopDat};
aligned_ready(stop, LoopDat) ->
timer:cancel(LoopDat#lsc_state.t1),
send_to(rc, stop, LoopDat),
send_to(suerm, stop, LoopDat),
send_to(txc, si_os, LoopDat),
{next_state, out_of_service, LoopDat};
aligned_ready({timer_expired, t1}, LoopDat) ->
send_to(l3, out_of_service, LoopDat),
send_to(rc, stop, LoopDat),
send_to(suerm, stop, LoopDat),
send_to(txc, si_os, LoopDat),
{next_state, out_of_service, LoopDat};
aligned_ready(si_po, LoopDat) ->
timer:cancel(LoopDat#lsc_state.t1),
send_to(l3, remote_processor_outage, LoopDat),
send_to(poc, remote_processor_outage, LoopDat),
{next_state, processor_outage, LoopDat};
aligned_ready(fisu_msu_received, LoopDat) ->
send_to(l3, in_service, LoopDat),
timer:cancel(LoopDat#lsc_state.t1),
send_to(txc, msu, LoopDat),
{next_state, in_service, LoopDat};
aligned_ready(What, LoopDat) when What == local_processor_outage;
What == level3_failure ->
send_to(poc, local_processor_outage, LoopDat),
send_to(txc, si_po, LoopDat),
send_to(rc, reject_msu_fisu, LoopDat),
{next_state, aligned_not_ready, LoopDat}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% STATE: aligned_not_ready
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
aligned_not_ready(Err, LoopDat) when Err == link_failure;
Err == si_o;
Err == si_os ->
timer:cancel(LoopDat#lsc_state.t1),
send_to(l3, out_of_service, LoopDat),
send_to(l3, stop, LoopDat),
send_to(suerm, stop, LoopDat),
send_to(txc, si_os, LoopDat),
send_to(poc, stop, LoopDat),
{next_state, out_of_service, LoopDat#lsc_state{local_proc_out=0, emergency=0}};
aligned_not_ready(stop, LoopDat) ->
timer:cancel(LoopDat#lsc_state.t1),
send_to(l3, stop, LoopDat),
send_to(suerm, stop, LoopDat),
send_to(txc, si_os, LoopDat),
send_to(poc, stop, LoopDat),
{next_state, out_of_service, LoopDat#lsc_state{local_proc_out=0, emergency=0}};
aligned_not_ready({timer_expired, t1}, LoopDat) ->
send_to(l3, stop, LoopDat),
send_to(suerm, stop, LoopDat),
send_to(txc, si_os, LoopDat),
send_to(poc, stop, LoopDat),
{next_state, out_of_service, LoopDat#lsc_state{local_proc_out=0, emergency=0}};
aligned_not_ready(local_processor_recovered, LoopDat) ->
send_to(poc, local_processor_recovered, LoopDat),
send_to(txc, fisu, LoopDat),
send_to(rc, accept_msu_fisu, LoopDat),
{next_state, aligned_ready, LoopDat#lsc_state{local_proc_out=0}};
aligned_not_ready(fisu_msu_received, LoopDat) ->
send_to(l3, in_service, LoopDat),
timer:cancel(LoopDat#lsc_state.t1),
{next_state, processor_outage, LoopDat};
aligned_not_ready(si_po, LoopDat) ->
send_to(l3, remote_processor_outage, LoopDat),
send_to(poc, remote_processor_outage, LoopDat),
timer:cancel(LoopDat#lsc_state.t1),
{next_state, processor_outage, LoopDat}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% STATE: in_service
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
in_service(What, LoopDat) when What == link_failure;
What == si_o;
What == si_n;
What == si_e;
What == si_os ->
send_to(l3, out_of_service, LoopDat),
send_to(suerm, stop, LoopDat),
send_to(rc, stop, LoopDat),
send_to(txc, si_os, LoopDat),
{next_state, out_of_service, LoopDat#lsc_state{emergency=0}};
in_service(stop, LoopDat) ->
send_to(suerm, stop, LoopDat),
send_to(rc, stop, LoopDat),
send_to(txc, si_os, LoopDat),
{next_state, out_of_service, LoopDat#lsc_state{emergency=0}};
in_service(What, LoopDat) when What == local_processor_outage;
What == level3_failure ->
send_to(poc, local_processor_outage, LoopDat),
send_to(txc, si_po, LoopDat),
send_to(rc, reject_msu_fisu, LoopDat),
{next_state, aligned_not_ready, LoopDat#lsc_state{local_proc_out=1}};
in_service(si_po, LoopDat) ->
send_to(txc, fisu, LoopDat),
send_to(l3, remote_processor_outage, LoopDat),
send_to(poc, remote_processor_outage, LoopDat),
{next_state, processor_outage, LoopDat#lsc_state{proc_out=1}}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% STATE: processor_outage
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
processor_outage(retrieval_request_and_fsnc, LoopDat) ->
send_to(txc, retrieval_request_and_fsnc, LoopDat),
{next_state, processor_outage, LoopDat};
processor_outage(fisu_msu_received, LoopDat) ->
send_to(poc, remote_processor_recovered, LoopDat),
send_to(l3, remote_processor_recovered, LoopDat),
{next_state, processor_outage, LoopDat};
processor_outage(retrieve_bsnt, LoopDat) ->
send_to(rc, retrieve_bsnt, LoopDat),
{next_state, processor_outage, LoopDat};
processor_outage(What, LoopDat) when What == local_processor_outage;
What == level3_failure ->
send_to(poc, local_processor_outage, LoopDat),
send_to(txc, si_po, LoopDat),
{next_state, processor_outage, LoopDat#lsc_state{local_proc_out=1}};
processor_outage(si_po, LoopDat) ->
send_to(l3, remote_processor_outage, LoopDat),
send_to(poc, remote_processor_outage, LoopDat),
{next_state, processor_outage, LoopDat#lsc_state{proc_out=1}};
processor_outage(local_processor_recovered, LoopDat) ->
send_to(poc, local_processor_recovered, LoopDat),
send_to(rc, retrieve_fsnx, LoopDat),
send_to(txc, fisu, LoopDat),
{next_state, processor_outage, LoopDat};
processor_outage(flush_buffers, LoopDat) ->
send_to(txc, flush_buffers, LoopDat),
% FIXME: mark L3 ind recv
{next_state, processor_outage, LoopDat};
processor_outage(no_processor_outage, LoopDat) ->
% FIXME: check L3 ind
send_to(txc, msu, LoopDat),
send_to(rc, accept_msu_fisu, LoopDat),
{next_state, in_service, LoopDat#lsc_state{local_proc_out=0, proc_out=0}};
processor_outage(What, LoopDat) when What == link_failure;
What == si_o;
What == si_n;
What == si_e;
What == si_os ->
send_to(l3, out_of_service, LoopDat),
send_to(suerm, stop, LoopDat),
send_to(rc, stop, LoopDat),
send_to(poc, stop, LoopDat),
send_to(txc, si_os, LoopDat),
{next_state, out_of_service, LoopDat#lsc_state{emergency=0, local_proc_out=0}};
processor_outage(stop, LoopDat) ->
send_to(suerm, stop, LoopDat),
send_to(rc, stop, LoopDat),
send_to(poc, stop, LoopDat),
send_to(txc, si_os, LoopDat),
{next_state, out_of_service, LoopDat#lsc_state{emergency=0, local_proc_out=0}}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% helper functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
send_to(txc, What, #lsc_state{txc_pid = Txc}) ->
Txc ! {lsc_txc, What};
send_to(iac, What, #lsc_state{iac_pid = Iac}) ->
gen_fsm:send_event(Iac, What);
send_to(rc, What, #lsc_state{rc_pid = Rc}) ->
Rc ! {lsc_rc, What};
send_to(Who, What, _LoopDat) ->
io:format("Not sending LSC -> ~p: ~p~n", [Who, What]).