1 /*
   2  * This file and its contents are supplied under the terms of the
   3  * Common Development and Distribution License ("CDDL"), version 1.0.
   4  * You may only use this file in accordance with the terms of version
   5  * 1.0 of the CDDL.
   6  *
   7  * A full copy of the text of the CDDL should have accompanied this
   8  * source.  A copy of the CDDL is also available via the Internet at
   9  * http://www.illumos.org/license/CDDL.
  10  */
  11 
  12 /*
  13  * Copyright 2013 David Hoeppner.  All rights reserved.
  14  */
  15 
  16 /*
  17  * Interrupt Load Balancer.
  18  *
  19  *
  20  */
  21 
  22 /* XXX
  23  *
  24  * intrd_cpu_list::walk list |::print intrd_cpu_t
  25  *
  26  *
  27  *
  28  */
  29 #include <sys/param.h>
  30 #include <sys/types.h>
  31 #include <sys/systm.h>
  32 #include <sys/apic.h>
  33 #include <sys/callb.h>
  34 #include <sys/cpuvar.h>
  35 #include <sys/proc.h>
  36 #include <sys/processor.h>
  37 #include <sys/sdt.h>
  38 #include <sys/sysmacros.h>
  39 #include <sys/time.h>
  40 
  41 extern  proc_t  *proc_intrd;
  42 
  43 #define INTRD_NAME              "intrd"
  44 
  45 #define IS_CPU(cpu_id)          (cpu[cpu_id] != NULL)
  46 
  47 #define INTRD_NORMAL_SLEEPTIME  10
  48 #define INTRD_IDLE_SLEEPTIME    45
  49 #define INTRD_ONECPU_SLEEPTIME  (60 * 15)
  50 
  51 #define INTRD_NUMBER_SAMPLES    10
  52 
  53 
  54 static kmutex_t         intrd_lock;
  55 static kcondvar_t       intrd_cv;
  56 
  57 /*
  58  * Interrupt CPU instance.
  59  */
  60 typedef struct _intrd_cpu {
  61         list_node_t     ic_next;
  62         boolean_t       ic_offline;
  63         hrtime_t        ic_tot[INTRD_NUMBER_SAMPLES];
  64         list_t          ic_ivec_list;
  65         processorid_t   ic_cpu_id;      /* XXX duplicate */
  66 } intrd_cpu_t;
  67 
  68 /*
  69  * Interrupt vector instance.
  70  */
  71 typedef struct _intrd_ivec {
  72         list_node_t     ii_next;
  73 } intrd_ivec_t;
  74 
  75 static list_t   intrd_cpu_list;         /* List of all CPU's */
  76 
  77 static uint8_t  intrd_cs = 0;           /* Index of current sample  */
  78 static long     intrd_sleeptime = INTRD_NORMAL_SLEEPTIME;
  79 
  80 /*
  81  * Function prototypes.
  82  */
  83 static  void    intrd_update(void *);
  84 static  void    intrd_cpu_register(processorid_t);
  85 static  int     intrd_cpu_setup(cpu_setup_t, int, void *);
  86 static  void    intrd_stat_all(void);
  87 
  88 /*
  89  * Helper macros.
  90  */
  91 #define FOREACH_INTRD_CPU(icpu, icpu_list)                      \
  92         for (icpu = list_head(&icpu_list); icpu != NULL;    \
  93             icpu = list_next(&icpu_list, icpu))
  94 
  95 #define DTRACE_INTRD(name)      \
  96         DTRACE_PROBE(__intrd_##name)
  97 
  98 #define DEBUG   1
  99 #ifdef  DEBUG
 100 #define INTRD_IMPLDBG(args)     cmn_err args
 101 #else
 102 #define INTRD_IMPLDBG(args)
 103 #endif
 104 
 105 void
 106 intrd(void)
 107 {
 108         processorid_t   cpu_id;
 109         callb_cpr_t     cpr;
 110         user_t          *u = PTOU(curproc);
 111 
 112         proc_intrd = ttoproc(curthread);
 113         proc_intrd->p_cstime = proc_intrd->p_stime = 0;
 114         proc_intrd->p_cutime = proc_intrd->p_utime = 0;
 115 
 116         (void) strncpy(u->u_psargs, INTRD_NAME, sizeof(u->u_psargs));
 117         (void) strncpy(u->u_comm, INTRD_NAME, sizeof(u->u_comm));
 118 
 119         /* Global mutex lock */
 120         mutex_init(&intrd_lock, NULL, MUTEX_DEFAULT, NULL);
 121 
 122         /* Initialize list for CPUs */
 123         list_create(&intrd_cpu_list, sizeof (intrd_cpu_t),
 124             offsetof(intrd_cpu_t, ic_next));
 125 
 126         /*
 127          * Build a list of all CPUs able for interrupt handling.
 128          */
 129         mutex_enter(&cpu_lock);
 130         for (cpu_id = 0; cpu_id <= max_cpu_seqid_ever; cpu_id++) {
 131                 if (IS_CPU(cpu_id))
 132                         intrd_cpu_register(cpu_id);
 133         }
 134 
 135         /*
 136          * Register a callback if a CPU goes offline or comes online.
 137          */
 138         register_cpu_setup_func(intrd_cpu_setup, NULL);
 139         mutex_exit(&cpu_lock);
 140 
 141         /* We should have at least one CPU */
 142         // ASSERT(!list_is_empty(&intrd_cpu_list));
 143         // ASSERT(intrd_number_cpus >= 1);
 144 
 145         CALLB_CPR_INIT(&cpr, &intrd_lock, callb_generic_cpr, INTRD_NAME);
 146 
 147         mutex_enter(&intrd_lock);
 148         for (;;) {
 149 
 150                 DTRACE_INTRD(stat_all);
 151                 intrd_stat_all();
 152 
 153                 CALLB_CPR_SAFE_BEGIN(&cpr);
 154                 cv_timedwait(&intrd_cv, &intrd_lock, ddi_get_lbolt() +
 155                     SEC_TO_TICK(intrd_sleeptime));
 156                 CALLB_CPR_SAFE_END(&cpr, &intrd_lock);
 157         }
 158 
 159         CALLB_CPR_EXIT(&cpr);
 160 
 161         /*
 162          * Unregister CPU callback.
 163          */
 164         mutex_enter(&cpu_lock);
 165         unregister_cpu_setup_func(intrd_cpu_setup, NULL);
 166         mutex_exit(&cpu_lock);
 167 }
 168 
 169 /*
 170  * Allocate a new intrd CPU structure and add CPU
 171  * to the global list of CPUs.
 172  */
 173 static void
 174 intrd_cpu_register(processorid_t cpu_id)
 175 {
 176         cpu_t           *cp = cpu[cpu_id];
 177         intrd_cpu_t     *new_cpu;
 178 
 179         new_cpu = kmem_alloc(sizeof (intrd_cpu_t), KM_SLEEP);
 180         new_cpu->ic_cpu_id = cpu_id;
 181 
 182         /* Initialize list for interrupt vectors */
 183         list_create(&new_cpu->ic_ivec_list, sizeof (intrd_ivec_t),
 184             offsetof(intrd_ivec_t, ii_next));
 185 
 186         list_link_init(&new_cpu->ic_next);
 187 
 188         /* Check if this CPU can handle interrupts */
 189         mutex_enter(&cpu_lock);
 190         if (cpu_is_nointr(cp))
 191                 new_cpu->ic_offline = B_TRUE;
 192         mutex_exit(&cpu_lock);
 193 
 194         /* Add new CPU to the list of all CPU's */
 195         list_insert_tail(&intrd_cpu_list, new_cpu);
 196 
 197         INTRD_IMPLDBG((CE_CONT, "intrd_cpu_register: cpu=0x%x", cpu_id));
 198 }
 199 
 200 /*
 201  * Remove a CPU from the global list of CPUs.
 202  */
 203 static void
 204 intrd_cpu_unregister(processorid_t cpu_id)
 205 {
 206         intrd_cpu_t     *icpu;
 207 
 208         mutex_enter(&intrd_lock);
 209         FOREACH_INTRD_CPU(icpu, intrd_cpu_list) {
 210                 if (icpu->ic_cpu_id == cpu_id) {
 211                         list_remove(&intrd_cpu_list, icpu);
 212                         /* XXX or just offline CPU; statistics? */
 213                         break;
 214                 }
 215         }
 216         mutex_exit(&intrd_lock);
 217 
 218         INTRD_IMPLDBG((CE_CONT, "intrd_cpu_unregister: cpu=0x%x",
 219             cpu_id));
 220 }
 221 
 222 /*
 223  * Hook for CPU changes.
 224  */
 225 static int
 226 intrd_cpu_setup(cpu_setup_t what, int cpu_id, void *arg)
 227 {
 228 
 229         switch (what) {
 230         /* XXX */
 231         case CPU_OFF:
 232                 intrd_cpu_unregister(cpu_id);
 233                 cv_signal(&intrd_cv);
 234                 break;
 235 
 236         case CPU_INTR_ON:
 237                 intrd_cpu_register(cpu_id);
 238                 cv_signal(&intrd_cv);
 239                 break;
 240 
 241         default:
 242                 break;
 243         }
 244 
 245         return (0);
 246 }
 247 
 248 static void
 249 intrd_cpu_stat(intrd_cpu_t *icpu)
 250 {
 251         cpu_t           *cp;
 252         hrtime_t        msnsecs[NCMSTATES];
 253 
 254         cp = cpu[icpu->ic_cpu_id];
 255         get_cpu_mstate(cp, msnsecs);
 256 
 257         icpu->ic_tot[intrd_cs] = msnsecs[CMS_IDLE] + msnsecs[CMS_USER] +
 258             msnsecs[CMS_SYSTEM];
 259 }
 260 
 261 static void
 262 intrd_irq_stat(intrd_cpu_t *icpu)
 263 {
 264         apic_irq_t      *irqp;
 265         int             irq_id;
 266 
 267         /* XXX: Do better then APIC_MAX_VECTOR */
 268         for (irq_id = APIC_FIRST_FREE_IRQ; irq_id < APIC_MAX_VECTOR;
 269             irq_id++) {
 270 /*
 271                 irqp = apic_irq_table[irq_id];
 272                 if (icpu->ic_cpu_id == (processorid_t)irqp->airq_cpu) {
 273                         INTRD_IMPLDBG((CE_CONT, "intrd_irq_stat: irq:0x%x",
 274                             irqp->airq_cpu));
 275                 }
 276 */
 277         }
 278 }
 279 
 280 static void
 281 intrd_stat_all(void)
 282 {
 283         intrd_cpu_t     *icpu;
 284 
 285         FOREACH_INTRD_CPU(icpu, intrd_cpu_list) {
 286                 intrd_cpu_stat(icpu);
 287                 intrd_irq_stat(icpu);
 288         }
 289 }
 290 
 291 static void
 292 intrd_interrupt_move(void)
 293 {
 294         /* XXX: pcitool_set_intr(dip, arg, mode); */
 295 }