vfs_disk_conditioner.c [plain text]
#include <sys/fsctl.h>
#include <stdbool.h>
#include <sys/time.h>
#include <sys/buf.h>
#include <sys/mount_internal.h>
#include <sys/vnode_internal.h>
#include <sys/buf_internal.h>
#include <kern/kalloc.h>
#include <sys/kauth.h>
#include <IOKit/IOBSD.h>
#include <vfs/vfs_disk_conditioner.h>
#define DISK_CONDITIONER_SET_ENTITLEMENT "com.apple.private.dmc.set"
#define BLK_MAX(mp) ((mp->mnt_vfsstat.f_blocks * mp->mnt_vfsstat.f_bsize) / (mp->mnt_devblocksize))
#define DISK_SPINUP_SEC (8)
#define DISK_IDLE_SEC (10 * 60)
struct _disk_conditioner_info_t {
boolean_t enabled; uint64_t access_time_usec; uint64_t read_throughput_mbps; uint64_t write_throughput_mbps; boolean_t is_ssd; daddr64_t last_blkno; struct timeval last_io_timestamp; };
void disk_conditioner_delay(buf_t, int, int, uint64_t);
void disk_conditioner_unmount(mount_t mp);
extern void throttle_info_mount_reset_period(mount_t, int isssd);
static double
weighted_scale_factor(double scale)
{
double x_m1 = scale - 1;
return x_m1 * x_m1 * x_m1 + 1;
}
void
disk_conditioner_delay(buf_t bp, int extents, int total_size, uint64_t already_elapsed_usec)
{
mount_t mp;
uint64_t delay_usec;
daddr64_t blkdiff;
daddr64_t last_blkno;
double access_time_scale;
struct _disk_conditioner_info_t *info = NULL;
struct timeval elapsed;
struct timeval start;
mp = buf_vnode(bp)->v_mount;
if (!mp) {
return;
}
info = mp->mnt_disk_conditioner_info;
if (!info || !info->enabled) {
return;
}
if (!info->is_ssd) {
last_blkno = info->last_blkno;
blkdiff = bp->b_blkno > last_blkno ? bp->b_blkno - last_blkno : last_blkno - bp->b_blkno;
info->last_blkno = bp->b_blkno + bp->b_bcount;
} else {
blkdiff = BLK_MAX(mp);
}
access_time_scale = weighted_scale_factor((double)blkdiff / BLK_MAX(mp));
delay_usec = (uint64_t)(((uint64_t)extents * info->access_time_usec) * access_time_scale);
if (info->read_throughput_mbps && (bp->b_flags & B_READ)) {
delay_usec += (uint64_t)(total_size / ((double)(info->read_throughput_mbps * 1024 * 1024 / 8) / USEC_PER_SEC));
} else if (info->write_throughput_mbps && !(bp->b_flags & B_READ)) {
delay_usec += (uint64_t)(total_size / ((double)(info->write_throughput_mbps * 1024 * 1024 / 8) / USEC_PER_SEC));
}
if (!info->is_ssd) {
microuptime(&elapsed);
timevalsub(&elapsed, &info->last_io_timestamp);
if (elapsed.tv_sec > DISK_IDLE_SEC && info->last_io_timestamp.tv_sec != 0) {
delay_usec += DISK_SPINUP_SEC * USEC_PER_SEC;
}
}
if (delay_usec <= already_elapsed_usec) {
microuptime(&info->last_io_timestamp);
return;
}
delay_usec -= already_elapsed_usec;
while (delay_usec) {
microuptime(&start);
delay(delay_usec);
microuptime(&elapsed);
timevalsub(&elapsed, &start);
if (elapsed.tv_sec * USEC_PER_SEC < delay_usec) {
delay_usec -= elapsed.tv_sec * USEC_PER_SEC;
} else {
break;
}
if ((uint64_t)elapsed.tv_usec < delay_usec) {
delay_usec -= elapsed.tv_usec;
} else {
break;
}
}
microuptime(&info->last_io_timestamp);
}
int
disk_conditioner_get_info(mount_t mp, disk_conditioner_info *uinfo)
{
struct _disk_conditioner_info_t *info;
if (!mp) {
return EINVAL;
}
info = mp->mnt_disk_conditioner_info;
if (!info) {
return 0;
}
uinfo->enabled = info->enabled;
uinfo->access_time_usec = info->access_time_usec;
uinfo->read_throughput_mbps = info->read_throughput_mbps;
uinfo->write_throughput_mbps = info->write_throughput_mbps;
uinfo->is_ssd = info->is_ssd;
return 0;
}
int
disk_conditioner_set_info(mount_t mp, disk_conditioner_info *uinfo)
{
struct _disk_conditioner_info_t *info;
if (!kauth_cred_issuser(kauth_cred_get()) || !IOTaskHasEntitlement(current_task(), DISK_CONDITIONER_SET_ENTITLEMENT)) {
return EPERM;
}
if (!mp) {
return EINVAL;
}
info = mp->mnt_disk_conditioner_info;
if (!info) {
info = mp->mnt_disk_conditioner_info = kalloc(sizeof(struct _disk_conditioner_info_t));
bzero(info, sizeof(struct _disk_conditioner_info_t));
}
info->enabled = uinfo->enabled;
info->access_time_usec = uinfo->access_time_usec;
info->read_throughput_mbps = uinfo->read_throughput_mbps;
info->write_throughput_mbps = uinfo->write_throughput_mbps;
info->is_ssd = uinfo->is_ssd;
microuptime(&info->last_io_timestamp);
throttle_info_mount_reset_period(mp, info->is_ssd);
return 0;
}
void
disk_conditioner_unmount(mount_t mp)
{
if (!mp->mnt_disk_conditioner_info) {
return;
}
kfree(mp->mnt_disk_conditioner_info, sizeof(struct _disk_conditioner_info_t));
mp->mnt_disk_conditioner_info = NULL;
}
boolean_t
disk_conditioner_mount_is_ssd(mount_t mp)
{
struct _disk_conditioner_info_t *info = mp->mnt_disk_conditioner_info;
if (!info || !info->enabled) {
return (mp->mnt_kern_flag & MNTK_SSD);
}
return info->is_ssd;
}