/**
 * @brief Main vmbus driver initialization routine.
 *
 * Here, we
 * - initialize the vmbus driver context
 * - setup various driver entry points
 * - invoke the vmbus hv main init routine
 * - get the irq resource
 * - invoke the vmbus to add the vmbus root device
 * - setup the vmbus root device
 * - retrieve the channel offers
 */
static int
vmbus_bus_init(void)
{
    struct ioapic_intsrc {
        struct intsrc io_intsrc;
        u_int io_irq;
        u_int io_intpin:8;
        u_int io_vector:8;
        u_int io_cpu:8;
        u_int io_activehi:1;
        u_int io_edgetrigger:1;
        u_int io_masked:1;
        int io_bus:4;
        uint32_t io_lowreg;
    };

    int ret;
    unsigned int vector = 0;
    struct intsrc *isrc;
    struct ioapic_intsrc *intpin;

    if (vmbus_inited)
        return (0);

    vmbus_inited = 1;

    ret = hv_vmbus_init();

    if (ret) {
        if(bootverbose)
            printf("Error VMBUS: Hypervisor Initialization Failed!\n");
        return (ret);
    }

    ret = swi_add(&hv_msg_intr_event, "hv_msg", vmbus_msg_swintr,
                  NULL, SWI_CLOCK, 0, &msg_swintr);

    if (ret)
        goto cleanup;

    /*
     * Message SW interrupt handler checks a per-CPU page and
     * thus the thread needs to be bound to CPU-0 - which is where
     * all interrupts are processed.
     */
    ret = intr_event_bind(hv_msg_intr_event, 0);

    if (ret)
        goto cleanup1;

    ret = swi_add(&hv_event_intr_event, "hv_event", hv_vmbus_on_events,
                  NULL, SWI_CLOCK, 0, &event_swintr);

    if (ret)
        goto cleanup1;

    intr_res = bus_alloc_resource(vmbus_devp,
                                  SYS_RES_IRQ, &vmbus_rid, vmbus_irq, vmbus_irq, 1, RF_ACTIVE);

    if (intr_res == NULL) {
        ret = ENOMEM; /* XXXKYS: Need a better errno */
        goto cleanup2;
    }

    /*
     * Setup interrupt filter handler
     */
    ret = bus_setup_intr(vmbus_devp, intr_res,
                         INTR_TYPE_NET | INTR_MPSAFE, hv_vmbus_isr, NULL,
                         NULL, &vmbus_cookiep);

    if (ret != 0)
        goto cleanup3;

    ret = bus_bind_intr(vmbus_devp, intr_res, 0);
    if (ret != 0)
        goto cleanup4;

    isrc = intr_lookup_source(vmbus_irq);
    if ((isrc == NULL) || (isrc->is_event == NULL)) {
        ret = EINVAL;
        goto cleanup4;
    }

    /* vector = isrc->is_event->ie_vector; */
    intpin = (struct ioapic_intsrc *)isrc;
    vector = intpin->io_vector;

    if(bootverbose)
        printf("VMBUS: irq 0x%x vector 0x%x\n", vmbus_irq, vector);

    /**
     * Notify the hypervisor of our irq.
     */

    smp_rendezvous(NULL, hv_vmbus_synic_init, NULL, &vector);

    /**
     * Connect to VMBus in the root partition
     */
    ret = hv_vmbus_connect();

    if (ret)
        goto cleanup4;

    hv_vmbus_request_channel_offers();
    return (ret);

cleanup4:

    /*
     * remove swi, bus and intr resource
     */
    bus_teardown_intr(vmbus_devp, intr_res, vmbus_cookiep);

cleanup3:

    bus_release_resource(vmbus_devp, SYS_RES_IRQ, vmbus_rid, intr_res);

cleanup2:
    swi_remove(event_swintr);

cleanup1:
    swi_remove(msg_swintr);

cleanup:
    hv_vmbus_cleanup();

    return (ret);
}
/**
 * @brief Main vmbus driver initialization routine.
 *
 * Here, we
 * - initialize the vmbus driver context
 * - setup various driver entry points
 * - invoke the vmbus hv main init routine
 * - get the irq resource
 * - invoke the vmbus to add the vmbus root device
 * - setup the vmbus root device
 * - retrieve the channel offers
 */
static int
vmbus_bus_init(void)
{
	int i, j, n, ret;

	if (vmbus_inited)
		return (0);

	vmbus_inited = 1;

	ret = hv_vmbus_init();

	if (ret) {
		if(bootverbose)
			printf("Error VMBUS: Hypervisor Initialization Failed!\n");
		return (ret);
	}

	/*
	 * Find a free IDT slot for vmbus callback.
	 */
	hv_vmbus_g_context.hv_cb_vector = vmbus_vector_alloc();

	if (hv_vmbus_g_context.hv_cb_vector == 0) {
		if(bootverbose)
			printf("Error VMBUS: Cannot find free IDT slot for "
			    "vmbus callback!\n");
		goto cleanup;
	}

	if(bootverbose)
		printf("VMBUS: vmbus callback vector %d\n",
		    hv_vmbus_g_context.hv_cb_vector);

	/*
	 * Notify the hypervisor of our vector.
	 */
	setup_args.vector = hv_vmbus_g_context.hv_cb_vector;

	CPU_FOREACH(j) {
		hv_vmbus_intr_cpu[j] = 0;
		hv_vmbus_swintr_event_cpu[j] = 0;
		hv_vmbus_g_context.hv_event_intr_event[j] = NULL;
		hv_vmbus_g_context.hv_msg_intr_event[j] = NULL;
		hv_vmbus_g_context.event_swintr[j] = NULL;
		hv_vmbus_g_context.msg_swintr[j] = NULL;

		for (i = 0; i < 2; i++)
			setup_args.page_buffers[2 * j + i] = NULL;
	}

	/*
	 * Per cpu setup.
	 */
	CPU_FOREACH(j) {
		/*
		 * Setup software interrupt thread and handler for msg handling.
		 */
		ret = swi_add(&hv_vmbus_g_context.hv_msg_intr_event[j],
		    "hv_msg", vmbus_msg_swintr, (void *)(long)j, SWI_CLOCK, 0,
		    &hv_vmbus_g_context.msg_swintr[j]);
		if (ret) {
			if(bootverbose)
				printf("VMBUS: failed to setup msg swi for "
				    "cpu %d\n", j);
			goto cleanup1;
		}

		/*
		 * Bind the swi thread to the cpu.
		 */
		ret = intr_event_bind(hv_vmbus_g_context.hv_msg_intr_event[j],
		    j);
	 	if (ret) {
			if(bootverbose)
				printf("VMBUS: failed to bind msg swi thread "
				    "to cpu %d\n", j);
			goto cleanup1;
		}

		/*
		 * Setup software interrupt thread and handler for
		 * event handling.
		 */
		ret = swi_add(&hv_vmbus_g_context.hv_event_intr_event[j],
		    "hv_event", hv_vmbus_on_events, (void *)(long)j,
		    SWI_CLOCK, 0, &hv_vmbus_g_context.event_swintr[j]);
		if (ret) {
			if(bootverbose)
				printf("VMBUS: failed to setup event swi for "
				    "cpu %d\n", j);
			goto cleanup1;
		}

		/*
		 * Prepare the per cpu msg and event pages to be called on each cpu.
		 */
		for(i = 0; i < 2; i++) {
			setup_args.page_buffers[2 * j + i] =
				malloc(PAGE_SIZE, M_DEVBUF, M_NOWAIT | M_ZERO);
			if (setup_args.page_buffers[2 * j + i] == NULL) {
				KASSERT(setup_args.page_buffers[2 * j + i] != NULL,
					("Error VMBUS: malloc failed!"));
				goto cleanup1;
			}
		}
	}

	if (bootverbose)
		printf("VMBUS: Calling smp_rendezvous, smp_started = %d\n",
		    smp_started);

	smp_rendezvous(NULL, hv_vmbus_synic_init, NULL, &setup_args);

	/*
	 * Connect to VMBus in the root partition
	 */
	ret = hv_vmbus_connect();

	if (ret != 0)
		goto cleanup1;

	hv_vmbus_request_channel_offers();
	return (ret);

	cleanup1:
	/*
	 * Free pages alloc'ed
	 */
	for (n = 0; n < 2 * MAXCPU; n++)
		if (setup_args.page_buffers[n] != NULL)
			free(setup_args.page_buffers[n], M_DEVBUF);

	/*
	 * remove swi and vmbus callback vector;
	 */
	CPU_FOREACH(j) {
		if (hv_vmbus_g_context.msg_swintr[j] != NULL)
			swi_remove(hv_vmbus_g_context.msg_swintr[j]);
		if (hv_vmbus_g_context.event_swintr[j] != NULL)
			swi_remove(hv_vmbus_g_context.event_swintr[j]);
		hv_vmbus_g_context.hv_msg_intr_event[j] = NULL;	
		hv_vmbus_g_context.hv_event_intr_event[j] = NULL;	
	}

	vmbus_vector_free(hv_vmbus_g_context.hv_cb_vector);

	cleanup:
	hv_vmbus_cleanup();

	return (ret);
}