@@ -29,6 +29,8 @@ local pulse = require "fibers.pulse"
2929--- @field initialised boolean
3030--- @field caps_applied boolean
3131--- @field state_pulse Pulse
32+ --- @field sim_inserted_pulse Pulse
33+ --- @field sim_state_ch Channel
3234local Modem = {}
3335Modem .__index = Modem
3436
@@ -45,6 +47,7 @@ local D_LOG_EMITTER = false
4547
4648local DEFAULT_STOP_TIMEOUT = 5
4749local DEFAULT_CACHE_TIMEOUT = 10
50+ local LISTEN_TRIGGER_INTERVAL = 1
4851
4952local CONTROL_Q_LEN = 8
5053
@@ -337,39 +340,37 @@ function Modem:listen_for_sim()
337340 end
338341 self .listening_for_sim = true
339342 local ok , err = fibers .current_scope ():spawn (function ()
340- fibers .run_scope (function ()
341- self :_emit_state (" sim_listener" , " open" )
342-
343- fibers .current_scope ():finally (function ()
344- self .listening_for_sim = false
345- self :_emit_state (" sim_listener" , " closed" )
346- end )
347-
348- while true do
349- --- out returns true if SIM is present, false if not
350- local source , out , err = fibers .perform (op .named_choice {
351- sim_present = self .backend :wait_for_sim_present_op (),
352- timeout = sleep .sleep_op (DEFAULT_CACHE_TIMEOUT )
353- })
354- if source == " sim_present" then
355- if err ~= " " then
356- log .error (" Modem Driver" , self .imei ,
357- " listen_for_sim: error waiting for SIM presence:" , err )
358- self :_emit_event (" sim_listen_error" , err )
359- end
360- if out then
361- self .state_pulse :signal ()
362- break
363- end
364- elseif source == " timeout" then
365- local ok , check_err = self .backend :trigger_sim_presence_check ()
366- if not ok then
367- log .error (" Modem Driver" , self .imei ,
368- " listen_for_sim: failed to trigger SIM presence check:" , check_err )
369- end
343+ self :_emit_state (" sim_listener" , " open" )
344+
345+ fibers .current_scope ():finally (function ()
346+ self .listening_for_sim = false
347+ self :_emit_state (" sim_listener" , " closed" )
348+ end )
349+
350+ -- Capture pulse version before reading state to close the insertion race window.
351+ local last_seen = self .sim_inserted_pulse :version ()
352+ local sim_present = self .sim_state_ch :get ()
353+
354+ while sim_present ~= true do
355+ local source , _ , primary = fibers .perform (op .named_choice {
356+ inserted = self .sim_inserted_pulse :changed_op (last_seen ),
357+ trigger = sleep .sleep_op (LISTEN_TRIGGER_INTERVAL ),
358+ failed = self .scope :fault_op (),
359+ })
360+ if source == " inserted" then
361+ break
362+ elseif source == " trigger" then
363+ local trigger_ok , check_err = self .backend :trigger_sim_presence_check ()
364+ if not trigger_ok then
365+ log .error (" Modem Driver" , self .imei ,
366+ " listen_for_sim: failed to trigger SIM presence check:" , check_err )
370367 end
368+ elseif source == " failed" then
369+ log .error (" Modem Driver" , self .imei ,
370+ " listen_for_sim: modem scope faulted:" , tostring (primary ))
371+ break
371372 end
372- end )
373+ end
373374 end )
374375 if not ok then
375376 return return_error (" listen_for_sim spawn failed: " .. tostring (err ), 1 )
@@ -520,6 +521,43 @@ function Modem:control_manager()
520521 end
521522end
522523
524+ function Modem :sim_lifecycle_monitor ()
525+ log .trace (" Modem Driver" , self .imei , " sim_lifecycle_monitor: started" )
526+
527+ fibers .current_scope ():finally (function ()
528+ log .trace (" Modem Driver" , self .imei , " sim_lifecycle_monitor: exiting" )
529+ end )
530+
531+ local sim_present = nil
532+ while true do
533+ local source , v1 , v2 = fibers .perform (op .named_choice {
534+ sim_change = self .backend :wait_for_sim_present_op (),
535+ send = self .sim_state_ch :put_op (sim_present ),
536+ })
537+ if source == " sim_change" then
538+ local present , err = v1 , v2
539+ if err == " cancelled" or err == " Stream closed" or err == " Command closed" then
540+ log .trace (" Modem Driver" , self .imei , " sim_lifecycle_monitor:" , err )
541+ break
542+ elseif err ~= " " then
543+ log .error (" Modem Driver" , self .imei , " sim_lifecycle_monitor: error polling SIM presence:" , err )
544+ sleep .sleep (DEFAULT_CACHE_TIMEOUT )
545+ elseif present then
546+ log .trace (" Modem Driver" , self .imei , " sim_lifecycle_monitor: SIM inserted" )
547+ sim_present = true
548+ self .sim_inserted_pulse :signal ()
549+ self .state_pulse :signal ()
550+ self :_emit_state (" sim_status" , " present" )
551+ else
552+ log .trace (" Modem Driver" , self .imei , " sim_lifecycle_monitor: SIM removed" )
553+ sim_present = false
554+ self :_emit_state (" sim_status" , " absent" )
555+ end
556+ end
557+ -- source == "send": listener consumed the state, loop to offer it again
558+ end
559+ end
560+
523561---- Driver Functions ----
524562
525563--- Spawn driver services
@@ -536,6 +574,7 @@ function Modem:start()
536574 self .scope :spawn (function () self :state_monitor () end )
537575 self .scope :spawn (function () self :control_manager () end )
538576 self .scope :spawn (function () self :emitter () end )
577+ self .scope :spawn (function () self :sim_lifecycle_monitor () end )
539578
540579 -- Signal initial pulse so emitter emits the initial state
541580 self .state_pulse :signal ()
@@ -668,6 +707,8 @@ local function new(address)
668707 caps_applied = false , -- modem cannot start until capabilities applied
669708 listening_for_sim = false ,
670709 state_pulse = pulse .new (),
710+ sim_inserted_pulse = pulse .new (),
711+ sim_state_ch = channel .new (),
671712 control_ch = control_ch
672713 }, Modem ), " "
673714end
0 commit comments