This is xnu-11215.1.10. See this file in:
/*
* Copyright (c) 2000-2020 Apple Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. The rights granted to you under the License
* may not be used to create, or enable the creation or redistribution of,
* unlawful or unlicensed copies of an Apple operating system, or to
* circumvent, violate, or enable the circumvention or violation of, any
* terms of an Apple operating system software license agreement.
*
* Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
*/
/*
* @OSF_COPYRIGHT@
*/
/*
* DEPRECATED INTERFACES - Should be removed
*
* Purpose: Routines for the creation and use of kernel
* alarm clock services. This file and the ipc
* routines in kern/ipc_clock.c constitute the
* machine-independent clock service layer.
*/
#include <mach/mach_types.h>
#include <kern/host.h>
#include <kern/spl.h>
#include <kern/sched_prim.h>
#include <kern/thread.h>
#include <kern/ipc_host.h>
#include <kern/clock.h>
#include <kern/zalloc.h>
#include <ipc/ipc_types.h>
#include <ipc/ipc_port.h>
#include <mach/mach_traps.h>
#include <mach/mach_time.h>
#include <mach/clock_server.h>
#include <mach/clock_reply.h>
#include <mach/mach_host_server.h>
#include <mach/host_priv_server.h>
#include <libkern/section_keywords.h>
/*
* Actual clock alarm structure. Used for user clock_sleep() and
* clock_alarm() calls. Alarms are allocated from the alarm free
* list and entered in time priority order into the active alarm
* chain of the target clock.
*/
struct alarm {
struct alarm *al_next; /* next alarm in chain */
struct alarm *al_prev; /* previous alarm in chain */
int al_status; /* alarm status */
mach_timespec_t al_time; /* alarm time */
struct { /* message alarm data */
int type; /* alarm type */
ipc_port_t port; /* alarm port */
mach_msg_type_name_t
port_type; /* alarm port type */
struct clock *clock; /* alarm clock */
void *data; /* alarm data */
} al_alrm;
#define al_type al_alrm.type
#define al_port al_alrm.port
#define al_port_type al_alrm.port_type
#define al_clock al_alrm.clock
#define al_data al_alrm.data
long al_seqno; /* alarm sequence number */
};
typedef struct alarm alarm_data_t;
/* alarm status */
#define ALARM_FREE 0 /* alarm is on free list */
#define ALARM_SLEEP 1 /* active clock_sleep() */
#define ALARM_CLOCK 2 /* active clock_alarm() */
#define ALARM_DONE 4 /* alarm has expired */
/* local data declarations */
decl_simple_lock_data(static, alarm_lock); /* alarm synchronization */
/* zone for user alarms */
static KALLOC_TYPE_DEFINE(alarm_zone, struct alarm, KT_DEFAULT);
static struct alarm *alrmfree; /* alarm free list pointer */
static struct alarm *alrmdone; /* alarm done list pointer */
static struct alarm *alrmlist;
static long alrm_seqno; /* uniquely identifies alarms */
static thread_call_data_t alarm_done_call;
static timer_call_data_t alarm_expire_timer;
extern struct clock clock_list[];
extern int clock_count;
static void post_alarm(
alarm_t alarm);
static void set_alarm(
mach_timespec_t *alarm_time);
static int check_time(
alarm_type_t alarm_type,
mach_timespec_t *alarm_time,
mach_timespec_t *clock_time);
static void alarm_done(void);
static void alarm_expire(void);
static kern_return_t clock_sleep_internal(
clock_t clock,
sleep_type_t sleep_type,
mach_timespec_t *sleep_time);
int rtclock_init(void);
kern_return_t rtclock_gettime(
mach_timespec_t *cur_time);
kern_return_t rtclock_getattr(
clock_flavor_t flavor,
clock_attr_t attr,
mach_msg_type_number_t *count);
SECURITY_READ_ONLY_EARLY(struct clock_ops) sysclk_ops = {
.c_config = NULL,
.c_init = rtclock_init,
.c_gettime = rtclock_gettime,
.c_getattr = rtclock_getattr,
};
kern_return_t calend_gettime(
mach_timespec_t *cur_time);
kern_return_t calend_getattr(
clock_flavor_t flavor,
clock_attr_t attr,
mach_msg_type_number_t *count);
SECURITY_READ_ONLY_EARLY(struct clock_ops) calend_ops = {
.c_config = NULL,
.c_init = NULL,
.c_gettime = calend_gettime,
.c_getattr = calend_getattr,
};
/*
* List of clock devices.
*/
SECURITY_READ_ONLY_LATE(struct clock) clock_list[] = {
[SYSTEM_CLOCK] = {
.cl_ops = &sysclk_ops,
.cl_service = IPC_PORT_NULL,
},
[CALENDAR_CLOCK] = {
.cl_ops = &calend_ops,
.cl_service = IPC_PORT_NULL,
},
};
int clock_count = sizeof(clock_list) / sizeof(clock_list[0]);
/*
* Macros to lock/unlock clock system.
*/
#define LOCK_ALARM(s) \
s = splclock(); \
simple_lock(&alarm_lock, LCK_GRP_NULL);
#define UNLOCK_ALARM(s) \
simple_unlock(&alarm_lock); \
splx(s);
void
clock_oldconfig(void)
{
clock_t clock;
int i;
simple_lock_init(&alarm_lock, 0);
thread_call_setup(&alarm_done_call, (thread_call_func_t)alarm_done, NULL);
timer_call_setup(&alarm_expire_timer, (timer_call_func_t)alarm_expire, NULL);
/*
* Configure clock devices.
*/
for (i = 0; i < clock_count; i++) {
clock = &clock_list[i];
if (clock->cl_ops && clock->cl_ops->c_config) {
if ((*clock->cl_ops->c_config)() == 0) {
clock->cl_ops = NULL;
}
}
}
/* start alarm sequence numbers at 0 */
alrm_seqno = 0;
}
void
clock_oldinit(void)
{
clock_t clock;
int i;
/*
* Initialize basic clock structures.
*/
for (i = 0; i < clock_count; i++) {
clock = &clock_list[i];
if (clock->cl_ops && clock->cl_ops->c_init) {
(*clock->cl_ops->c_init)();
}
}
}
/*
* Initialize the clock ipc service facility.
*/
void
clock_service_create(void)
{
/*
* Initialize ipc clock services.
*/
for (int i = 0; i < clock_count; i++) {
clock_t clock = &clock_list[i];
if (clock->cl_ops) {
ipc_clock_init(clock);
}
}
}
/*
* Get the service port on a clock.
*/
kern_return_t
host_get_clock_service(
host_t host,
clock_id_t clock_id,
clock_t *clock) /* OUT */
{
if (host == HOST_NULL || clock_id < 0 || clock_id >= clock_count) {
*clock = CLOCK_NULL;
return KERN_INVALID_ARGUMENT;
}
*clock = &clock_list[clock_id];
if ((*clock)->cl_ops == 0) {
return KERN_FAILURE;
}
return KERN_SUCCESS;
}
/*
* Get the current clock time.
*/
kern_return_t
clock_get_time(
clock_t clock,
mach_timespec_t *cur_time) /* OUT */
{
if (clock == CLOCK_NULL) {
return KERN_INVALID_ARGUMENT;
}
return (*clock->cl_ops->c_gettime)(cur_time);
}
kern_return_t
rtclock_gettime(
mach_timespec_t *time) /* OUT */
{
clock_sec_t secs;
clock_nsec_t nsecs;
clock_get_system_nanotime(&secs, &nsecs);
time->tv_sec = (unsigned int)secs;
time->tv_nsec = nsecs;
return KERN_SUCCESS;
}
kern_return_t
calend_gettime(
mach_timespec_t *time) /* OUT */
{
clock_sec_t secs;
clock_nsec_t nsecs;
clock_get_calendar_nanotime(&secs, &nsecs);
time->tv_sec = (unsigned int)secs;
time->tv_nsec = nsecs;
return KERN_SUCCESS;
}
/*
* Get clock attributes.
*/
kern_return_t
clock_get_attributes(
clock_t clock,
clock_flavor_t flavor,
clock_attr_t attr, /* OUT */
mach_msg_type_number_t *count) /* IN/OUT */
{
if (clock == CLOCK_NULL) {
return KERN_INVALID_ARGUMENT;
}
if (clock->cl_ops->c_getattr) {
return clock->cl_ops->c_getattr(flavor, attr, count);
}
return KERN_FAILURE;
}
kern_return_t
rtclock_getattr(
clock_flavor_t flavor,
clock_attr_t attr, /* OUT */
mach_msg_type_number_t *count) /* IN/OUT */
{
if (*count != 1) {
return KERN_FAILURE;
}
switch (flavor) {
case CLOCK_GET_TIME_RES: /* >0 res */
case CLOCK_ALARM_CURRES: /* =0 no alarm */
case CLOCK_ALARM_MINRES:
case CLOCK_ALARM_MAXRES:
*(clock_res_t *) attr = NSEC_PER_SEC / 100;
break;
default:
return KERN_INVALID_VALUE;
}
return KERN_SUCCESS;
}
kern_return_t
calend_getattr(
clock_flavor_t flavor,
clock_attr_t attr, /* OUT */
mach_msg_type_number_t *count) /* IN/OUT */
{
if (*count != 1) {
return KERN_FAILURE;
}
switch (flavor) {
case CLOCK_GET_TIME_RES: /* >0 res */
*(clock_res_t *) attr = NSEC_PER_SEC / 100;
break;
case CLOCK_ALARM_CURRES: /* =0 no alarm */
case CLOCK_ALARM_MINRES:
case CLOCK_ALARM_MAXRES:
*(clock_res_t *) attr = 0;
break;
default:
return KERN_INVALID_VALUE;
}
return KERN_SUCCESS;
}
/*
* Setup a clock alarm.
*/
kern_return_t
clock_alarm(
clock_t clock,
alarm_type_t alarm_type,
mach_timespec_t alarm_time,
ipc_port_t alarm_port,
mach_msg_type_name_t alarm_port_type)
{
alarm_t alarm;
mach_timespec_t clock_time;
int chkstat;
kern_return_t reply_code;
spl_t s;
if (clock == CLOCK_NULL) {
return KERN_INVALID_ARGUMENT;
}
if (clock != &clock_list[SYSTEM_CLOCK]) {
return KERN_FAILURE;
}
if (IP_VALID(alarm_port) == 0) {
return KERN_INVALID_CAPABILITY;
}
/*
* Check alarm parameters. If parameters are invalid,
* send alarm message immediately.
*/
(*clock->cl_ops->c_gettime)(&clock_time);
chkstat = check_time(alarm_type, &alarm_time, &clock_time);
if (chkstat <= 0) {
reply_code = (chkstat < 0 ? KERN_INVALID_VALUE : KERN_SUCCESS);
clock_alarm_reply(alarm_port, alarm_port_type,
reply_code, alarm_type, clock_time);
return KERN_SUCCESS;
}
/*
* Get alarm and add to clock alarm list.
*/
LOCK_ALARM(s);
if ((alarm = alrmfree) == 0) {
UNLOCK_ALARM(s);
alarm = zalloc_flags(alarm_zone, Z_WAITOK | Z_NOFAIL);
LOCK_ALARM(s);
} else {
alrmfree = alarm->al_next;
}
alarm->al_status = ALARM_CLOCK;
alarm->al_time = alarm_time;
alarm->al_type = alarm_type;
alarm->al_port = alarm_port;
alarm->al_port_type = alarm_port_type;
alarm->al_clock = clock;
alarm->al_seqno = alrm_seqno++;
post_alarm(alarm);
UNLOCK_ALARM(s);
return KERN_SUCCESS;
}
/*
* Sleep on a clock. System trap. User-level libmach clock_sleep
* interface call takes a mach_timespec_t sleep_time argument which it
* converts to sleep_sec and sleep_nsec arguments which are then
* passed to clock_sleep_trap.
*/
kern_return_t
clock_sleep_trap(
struct clock_sleep_trap_args *args)
{
mach_port_name_t clock_name = args->clock_name;
sleep_type_t sleep_type = args->sleep_type;
int sleep_sec = args->sleep_sec;
int sleep_nsec = args->sleep_nsec;
mach_vm_address_t wakeup_time_addr = args->wakeup_time;
clock_t clock;
mach_timespec_t swtime = {};
kern_return_t rvalue;
/*
* Convert the trap parameters.
*/
if (clock_name == MACH_PORT_NULL) {
clock = &clock_list[SYSTEM_CLOCK];
} else {
clock = port_name_to_clock(clock_name);
}
swtime.tv_sec = sleep_sec;
swtime.tv_nsec = sleep_nsec;
/*
* Call the actual clock_sleep routine.
*/
rvalue = clock_sleep_internal(clock, sleep_type, &swtime);
/*
* Return current time as wakeup time.
*/
if (rvalue != KERN_INVALID_ARGUMENT && rvalue != KERN_FAILURE) {
copyout((char *)&swtime, wakeup_time_addr, sizeof(mach_timespec_t));
}
return rvalue;
}
static kern_return_t
clock_sleep_internal(
clock_t clock,
sleep_type_t sleep_type,
mach_timespec_t *sleep_time)
{
alarm_t alarm;
mach_timespec_t clock_time;
kern_return_t rvalue;
int chkstat;
spl_t s;
if (clock == CLOCK_NULL) {
return KERN_INVALID_ARGUMENT;
}
if (clock != &clock_list[SYSTEM_CLOCK]) {
return KERN_FAILURE;
}
/*
* Check sleep parameters. If parameters are invalid
* return an error, otherwise post alarm request.
*/
(*clock->cl_ops->c_gettime)(&clock_time);
chkstat = check_time(sleep_type, sleep_time, &clock_time);
if (chkstat < 0) {
return KERN_INVALID_VALUE;
}
rvalue = KERN_SUCCESS;
if (chkstat > 0) {
wait_result_t wait_result;
/*
* Get alarm and add to clock alarm list.
*/
LOCK_ALARM(s);
if ((alarm = alrmfree) == 0) {
UNLOCK_ALARM(s);
alarm = zalloc_flags(alarm_zone, Z_WAITOK | Z_NOFAIL);
LOCK_ALARM(s);
} else {
alrmfree = alarm->al_next;
}
/*
* Wait for alarm to occur.
*/
wait_result = assert_wait((event_t)alarm, THREAD_ABORTSAFE);
if (wait_result == THREAD_WAITING) {
alarm->al_time = *sleep_time;
alarm->al_status = ALARM_SLEEP;
post_alarm(alarm);
UNLOCK_ALARM(s);
wait_result = thread_block(THREAD_CONTINUE_NULL);
/*
* Note if alarm expired normally or whether it
* was aborted. If aborted, delete alarm from
* clock alarm list. Return alarm to free list.
*/
LOCK_ALARM(s);
if (alarm->al_status != ALARM_DONE) {
assert(wait_result != THREAD_AWAKENED);
if (((alarm->al_prev)->al_next = alarm->al_next) != NULL) {
(alarm->al_next)->al_prev = alarm->al_prev;
}
rvalue = KERN_ABORTED;
}
*sleep_time = alarm->al_time;
alarm->al_status = ALARM_FREE;
} else {
assert(wait_result == THREAD_INTERRUPTED);
assert(alarm->al_status == ALARM_FREE);
rvalue = KERN_ABORTED;
}
alarm->al_next = alrmfree;
alrmfree = alarm;
UNLOCK_ALARM(s);
} else {
*sleep_time = clock_time;
}
return rvalue;
}
/*
* Service clock alarm expirations.
*/
static void
alarm_expire(void)
{
clock_t clock;
alarm_t alrm1;
alarm_t alrm2;
mach_timespec_t clock_time;
mach_timespec_t *alarm_time;
spl_t s;
clock = &clock_list[SYSTEM_CLOCK];
(*clock->cl_ops->c_gettime)(&clock_time);
/*
* Update clock alarm list. Alarms that are due are moved
* to the alarmdone list to be serviced by a thread callout.
*/
LOCK_ALARM(s);
alrm1 = (alarm_t)&alrmlist;
while ((alrm2 = alrm1->al_next) != NULL) {
alarm_time = &alrm2->al_time;
if (CMP_MACH_TIMESPEC(alarm_time, &clock_time) > 0) {
break;
}
/*
* Alarm has expired, so remove it from the
* clock alarm list.
*/
if ((alrm1->al_next = alrm2->al_next) != NULL) {
(alrm1->al_next)->al_prev = alrm1;
}
/*
* If a clock_sleep() alarm, wakeup the thread
* which issued the clock_sleep() call.
*/
if (alrm2->al_status == ALARM_SLEEP) {
alrm2->al_next = NULL;
alrm2->al_status = ALARM_DONE;
alrm2->al_time = clock_time;
thread_wakeup((event_t)alrm2);
}
/*
* If a clock_alarm() alarm, place the alarm on
* the alarm done list and schedule the alarm
* delivery mechanism.
*/
else {
assert(alrm2->al_status == ALARM_CLOCK);
if ((alrm2->al_next = alrmdone) != NULL) {
alrmdone->al_prev = alrm2;
} else {
thread_call_enter(&alarm_done_call);
}
alrm2->al_prev = (alarm_t)&alrmdone;
alrmdone = alrm2;
alrm2->al_status = ALARM_DONE;
alrm2->al_time = clock_time;
}
}
/*
* Setup to expire for the next pending alarm.
*/
if (alrm2) {
set_alarm(alarm_time);
}
UNLOCK_ALARM(s);
}
static void
alarm_done(void)
{
alarm_t alrm;
kern_return_t code;
spl_t s;
LOCK_ALARM(s);
while ((alrm = alrmdone) != NULL) {
if ((alrmdone = alrm->al_next) != NULL) {
alrmdone->al_prev = (alarm_t)&alrmdone;
}
UNLOCK_ALARM(s);
code = (alrm->al_status == ALARM_DONE? KERN_SUCCESS: KERN_ABORTED);
if (alrm->al_port != IP_NULL) {
/* Deliver message to designated port */
if (IP_VALID(alrm->al_port)) {
clock_alarm_reply(alrm->al_port, alrm->al_port_type, code,
alrm->al_type, alrm->al_time);
}
LOCK_ALARM(s);
alrm->al_status = ALARM_FREE;
alrm->al_next = alrmfree;
alrmfree = alrm;
} else {
panic("clock_alarm_deliver");
}
}
UNLOCK_ALARM(s);
}
/*
* Post an alarm on the active alarm list.
*
* Always called from within a LOCK_ALARM() code section.
*/
static void
post_alarm(
alarm_t alarm)
{
alarm_t alrm1, alrm2;
mach_timespec_t *alarm_time;
mach_timespec_t *queue_time;
/*
* Traverse alarm list until queue time is greater
* than alarm time, then insert alarm.
*/
alarm_time = &alarm->al_time;
alrm1 = (alarm_t)&alrmlist;
while ((alrm2 = alrm1->al_next) != NULL) {
queue_time = &alrm2->al_time;
if (CMP_MACH_TIMESPEC(queue_time, alarm_time) > 0) {
break;
}
alrm1 = alrm2;
}
alrm1->al_next = alarm;
alarm->al_next = alrm2;
alarm->al_prev = alrm1;
if (alrm2) {
alrm2->al_prev = alarm;
}
/*
* If the inserted alarm is the 'earliest' alarm,
* reset the device layer alarm time accordingly.
*/
if (alrmlist == alarm) {
set_alarm(alarm_time);
}
}
static void
set_alarm(
mach_timespec_t *alarm_time)
{
uint64_t abstime;
nanotime_to_absolutetime(alarm_time->tv_sec, alarm_time->tv_nsec, &abstime);
timer_call_enter_with_leeway(&alarm_expire_timer, NULL, abstime, 0, TIMER_CALL_USER_NORMAL, FALSE);
}
/*
* Check the validity of 'alarm_time' and 'alarm_type'. If either
* argument is invalid, return a negative value. If the 'alarm_time'
* is now, return a 0 value. If the 'alarm_time' is in the future,
* return a positive value.
*/
static int
check_time(
alarm_type_t alarm_type,
mach_timespec_t *alarm_time,
mach_timespec_t *clock_time)
{
int result;
if (BAD_ALRMTYPE(alarm_type)) {
return -1;
}
if (BAD_MACH_TIMESPEC(alarm_time)) {
return -1;
}
if ((alarm_type & ALRMTYPE) == TIME_RELATIVE) {
ADD_MACH_TIMESPEC(alarm_time, clock_time);
}
result = CMP_MACH_TIMESPEC(alarm_time, clock_time);
return (result >= 0)? result: 0;
}
#ifndef __LP64__
mach_timespec_t
clock_get_system_value(void)
{
clock_t clock = &clock_list[SYSTEM_CLOCK];
mach_timespec_t value;
(void) (*clock->cl_ops->c_gettime)(&value);
return value;
}
mach_timespec_t
clock_get_calendar_value(void)
{
clock_t clock = &clock_list[CALENDAR_CLOCK];
mach_timespec_t value = MACH_TIMESPEC_ZERO;
(void) (*clock->cl_ops->c_gettime)(&value);
return value;
}
#endif /* __LP64__ */