str-ctl 实现源码
AMSS/platform/services/applications/str-ctl/main.c
int main ( int argc, char** argv)
{
int c ;
uint64_t entry_timeout_nsec = 1000000000ull;
uint64_t wakeup_timeout_nsec = 1000000000ull;
bool initiate = false ;
while( ( c = getopt( argc, argv, "e:w:i" ) ) != -1 ) {
switch( c ) {
case 'e':
entry_timeout_nsec = strtoull ( optarg, NULL, 0 );
break;
case 'w':
wakeup_timeout_nsec = strtoull ( optarg, NULL, 0 );
break;
case 'i':
initiate = true;
break;
case '?':
ERR("Invalid option(%c), see \"use %s\"", c, argv[0]);
return -1;
}
}
if ( !initiate ) {
ERR("'-i' option mandatory, see \"use %s\"", argv[0]);
return EOK ;
}
int fd = open ( "/dev/pm", O_RDWR | O_CLOEXEC) ;
if ( fd < 0 ) {
ERR ("open(/dev/pm) failed, err=%d", errno);
return -1;
}
struct timespec start, stop;
uint64_t cycle, ncycles;
uint64_t duration_nsec ;
struct pm_str_s cmd = {0};
cmd.entry_timeout = entry_timeout_nsec ;
cmd.wakeup_timeout = wakeup_timeout_nsec ;
(void) clock_gettime( CLOCK_REALTIME, &start) ;
cycle = ClockCycles();
int const rc = devctl ( fd, DCMD_PM_ENTER_STR , &cmd, sizeof (struct pm_str_s), 0 );
ncycles = ClockCycles() - cycle;
(void) clock_gettime( CLOCK_REALTIME, &stop) ;
duration_nsec = timespec2nsec (&stop) - timespec2nsec (&start);
if ( rc == EOK && duration_nsec < wakeup_timeout_nsec ) {
LOG("STR interrupted, expected to sleep=%llu ms, actual sleep =%llu ms",
NSEC_TO_MSEC ( wakeup_timeout_nsec ), NSEC_TO_MSEC (duration_nsec ));
}
LOG("DCMD_PM_ENTER_STR %s rc=%d", rc ? "failed" : "succeeded" , rc );
LOG("Elapsed system time -> %fs", 1.0f * duration_nsec / ( BILLION * 1.0f) );
LOG("Elapsed raw cycles time -> %fs", (double) ncycles / SYSPAGE_ENTRY(qtime)->cycles_per_sec);
close ( fd ) ;
return rc ;
}
str-ctl 触发整机休眠
qcom 参考文档
QNX Power Management Software Architecture Reference Manual 80-PG469-7 Rev. N
October 5, 2022
3.6.2 Suspend QNX host Before the device can be put into LPM,
terminate WFD and touch clients and turn off the displays that are connected to the system by running the command. NOTE: The following commands to slay ‘calib-touch’ and ‘openwfd_telltale’ are just for test purposes. In general, the expectation is that GFX/HMI related applications quiesced and are aware of the various system power states such that they do not make any screen_*() or openGL calls once the suspend to RAM sequence is kicked off by the OEM’s lifecyclemanager. Contact QTI support for more information.
# slay calib-touch
# slay openwfd_telltale
# screen-pwrmgr off
Turn off the USB controller that is managed by the host OS by running the command:
# echo "BUS::stop,busno=0" >> /var/pps/device/usb_ctrl1
# echo "BUS::stop,busno=0" >> /var/pps/device/usb_ctrl2
The final step is to kick off the STR sequence by running the command:
# str-ctl -i -e 5000000000 -w 5000000000
A successful STR sequence looks like:
# str-ctl -i -e 5000000000 -w 5000000000
[main:81] DCMD_PM_ENTER_STR succeeded rc=0
[main:82] Elapsed system time -> 5.284297s
[main:83] Elapsed raw cycles time -> 5.284475s
AMSS/platform/power/powermgr/src/pwrmgr.c
static int io_devctl (resmgr_context_t *ctp, io_devctl_t *msg,
RESMGR_OCB_T *_ocb)
{
struct pm_ocb_s* ocb = (struct pm_ocb_s*)_ocb;
struct pm_dev_s *dev = ocb->dev;
int rc = iofunc_devctl_default(ctp, msg, (iofunc_ocb_t *)ocb);
if (rc != _RESMGR_DEFAULT)
{
ERROR("devctl_default verify failed : %s",
strerror(errno));
return rc;
}
if (msg == NULL)
{
ERROR("Invalid parameter");
return EINVAL;
}
const int dcmd = msg->i.dcmd;
switch (dcmd) {
case DCMD_PM_REGISTER:
{
return pm_register_client(ctp, msg, ocb);
}
case DCMD_PM_ACK:
{
return pm_handle_ack(ocb, msg, dev);
}
case DCMD_PM_ENTER_STR:
{
return pm_enter_str(ctp, msg, dev);
}
case DCMD_PM_RESUME:
{
return pm_resume_fsm ( dev, EOK ) ;
}
default:
ERROR("Invalid DCMD(0x%x)", dcmd ) ;
return ENOSYS;
}
}
static int pm_enter_str(resmgr_context_t *ctp, io_devctl_t *msg, struct pm_dev_s *dev)
{
int rc = EOK;
if ( msg->i.nbytes != sizeof (struct pm_str_s)){
INFO("Invalid size");
return EINVAL;
}
if ( fsm_is_active (dev) ) {
INFO("DCMD_PM_ENTER_STR already under progress");
return EBUSY;
}
if ( ! dev->nclients ) {
INFO("No registered clients");
return ESRCH;
}
const struct pm_str_s *cmd = ( const struct pm_str_s *) _DEVCTL_DATA( msg->i );
INFO("DCMD_PM_ENTER_STR start (entry_timeout=%lu ms, wakeup_timeout=%lu ms)",
cmd->entry_timeout / ( 1000 * 1000) , cmd->wakeup_timeout / ( 1000 * 1000));
rc = pm_settimer ( dev->timerid , cmd->entry_timeout ) ;
if ( rc != EOK ) {
ERROR("pm_settimer() failed rc=%d", rc);
return rc ;
}
dev->str_time.start = ClockCycles();
const enum fsm_result result = fsm_perform ( dev ) ;
switch ( result ) {
case FSM_IN_PROGRESS:
break ;
case FSM_TERMINATED:
rc = fsm_get_initiator_rc ( dev );
(void) fsm_conclude ( dev ) ;
return rc ;
default:
(void) pm_settimer ( dev->timerid , 0);
return EIO ;
}
pm_setwakeup ( dev, cmd->wakeup_timeout);
fsm_set_initiator_rc (dev, EOK ) ;
fsm_set_initiator_rcvid (dev, ctp->rcvid);
return (_RESMGR_NOREPLY );
}
单模块suspend resume
# Deliver prepare & suspend pulses to the audio driver by running
# below commands
# This is functionally similar to what the framework does during
# full system suspend.
echo prepare > /dev/pdbg/qcore/power/clients/audio_mcm.0
echo suspend > /dev/pdbg/qcore/power/clients/audio_mcm.0
# At this point if all active audio sessions are terminated then aDSP should power collapse itself. # Deliver resume and complete pulses by running below commands.
# This is functionally similar to what the framework does during
# full system resume.
echo resume > /dev/pdbg/qcore/power/clients/audio_mcm.0
echo complete > /dev/pdbg/qcore/power/clients/audio_mcm.0
#chmod 777 camera_server_pm.0
#camera suspend
echo prepare > /dev/pdbg/qcore/power/clients/camera_server_pm.0
echo suspend > /dev/pdbg/qcore/power/clients/camera_server_pm.0
#camera resume
echo resume > /dev/pdbg/qcore/power/clients/camera_server_pm.0
echo complete > /dev/pdbg/qcore/power/clients/camera_server_pm.0
//动态创建client
/dev/pdbg/qcore/power/clients/(client->name)
//提供动态模块
suspend resume 流程debug 节点
static int register_a_client ( struct pm_ocb_s *ocb,
const struct pm_register_s *req, pid_t pid )
{
struct pm_dev_s *dev = ocb->dev;
if (req->priority >= PM_PRIO_LEVEL_MAX) {
return EINVAL;
}
if ( is_internal_level (req->priority)){
return EINVAL;
}
#define PM_VALID_FLAGS (PM_FLAG_NO_SUSPEND)
if ( req->flags & (~PM_VALID_FLAGS) ){
return EINVAL;
}
//Validate the pulse codes for collisions,
//this should weed out any clients that do not
//use INIT_PM_REGISTER_STRUCT() to setup the
//register structure.
for ( unsigned i = 0 ; i < PM_STATE_MAX ; i++ ) {
if ( req->pulse_codes [i] < _PULSE_CODE_MINAVAIL ||
req->pulse_codes [i] > _PULSE_CODE_MAXAVAIL )
continue ;
for ( unsigned j = i + 1 ; j < PM_STATE_MAX ; j ++ ) {
if ( req->pulse_codes[i] == req->pulse_codes[j] ) {
ERROR ("Pulse code collision of type \"%s(%d)\" with \"%s(%d)\"",
pm_state_to_str [i] , req->pulse_codes[i],
pm_state_to_str [j] , req->pulse_codes[j] );
return EINVAL;
}
}
}
struct pm_client_s *client = calloc ( 1 , sizeof (*client));
if ( !client){
return ENOMEM;
}
snprintf( client->name, sizeof (client->name) , "%s.%d",
req->name, get_num_clients_per_pid (dev, pid));
client->pid = pid;
for ( unsigned i = 0 ; i < PM_STATE_MAX ; i++ ) {
client->pulse_codes[i] = req->pulse_codes[i] ;
}
pthread_mutex_init (&client->wait.mtx , NULL);
pthread_condattr_t attr;
pthread_condattr_init( &attr);
pthread_condattr_setclock( &attr, CLOCK_MONOTONIC);
pthread_cond_init( &client->wait.cond, &attr);
client->prio_level = req->priority;
client->flags = req->flags ;
client->coid = ConnectAttach(0, pid , req->chid ,
_NTO_SIDE_CHANNEL,
_NTO_COF_CLOEXEC);
if ( client->coid == -1 ) {
ERROR( "ConnectAttach(pid=%d,chid=%d) failed, err=%d",
pid, req->chid, errno ) ;
free ( client ) ;
return EINVAL;
}
ocb->self = client ;
pthread_mutex_lock ( &dev->lock ) ;
list_add_head ( &dev->client_heads[ req->priority ], &client->node ) ;
dev->nclients ++;
struct pdbg_ops_s pwr_client_votes = { 0 };
pwr_client_votes.write2 = &pm_pdbg_ctl;
char tmp[128];
snprintf (tmp, sizeof(tmp) , "power/clients/%s", client->name);
//动态创建 /dev/pdbg/qcore/power/clients/(client->name)
//提供动态模块suspend resume 流程debug 节点
pdbg_create_file (tmp, 0220 , pwr_client_votes , (void * ) client );
pthread_mutex_unlock ( &dev->lock ) ;
INFO("Registered external client=\"%s\" at prio=%d", client->name,
req->priority ) ;
return EOK ;
}