root/src/add-ons/kernel/bus_managers/scsi/busses.cpp
/*
 * Copyright 2002/03, Thomas Kurschel. All rights reserved.
 * Distributed under the terms of the MIT License.
 */

/*
        Part of Open SCSI bus manager

        Bus node layer.

        Whenever a controller driver publishes a new controller, a new SCSI bus
        for public and internal use is registered in turn. After that, this
        bus is told to rescan for devices. For each device, there is a
        device registered for peripheral drivers. (see devices.c)
*/

#include "scsi_internal.h"

#include <string.h>
#include <malloc.h>


// bus service should hurry up a bit - good controllers don't take much time
// but are very happy to be busy; don't make it realtime though as we
// don't really need that but would risk to steel processing power of
// realtime-demanding threads
#define BUS_SERVICE_PRIORITY B_URGENT_DISPLAY_PRIORITY


/**     implementation of service thread:
 *      it handles DPC and pending requests
 */

static void
scsi_do_service(scsi_bus_info *bus)
{
        while (true) {
                SHOW_FLOW0( 3, "" );

                // handle DPCs first as they are more urgent
                if (scsi_check_exec_dpc(bus))
                        continue;

                if (scsi_check_exec_service(bus))
                        continue;

                break;
        }
}


/** main loop of service thread */

static int32
scsi_service_threadproc(void *arg)
{
        scsi_bus_info *bus = (scsi_bus_info *)arg;
        int32 processed_notifications = 0;

        SHOW_FLOW(3, "bus = %p", bus);

        while (true) {
                // we handle multiple requests in scsi_do_service at once;
                // to save time, we will acquire all notifications that are sent
                // up to now at once.
                // (Sadly, there is no "set semaphore to zero" function, so this
                //  is a poor-man emulation)
                acquire_sem_etc(bus->start_service, processed_notifications + 1, 0, 0);

                SHOW_FLOW0( 3, "1" );

                if (bus->shutting_down)
                        break;

                // get number of notifications _before_ servicing to make sure no new
                // notifications are sent after do_service()
                get_sem_count(bus->start_service, &processed_notifications);

                scsi_do_service(bus);
        }

        return 0;
}


static scsi_bus_info *
scsi_create_bus(device_node *node, uint8 path_id)
{
        scsi_bus_info *bus;
        int res;

        SHOW_FLOW0(3, "");

        bus = (scsi_bus_info *)malloc(sizeof(*bus));
        if (bus == NULL)
                return NULL;

        memset(bus, 0, sizeof(*bus));

        bus->path_id = path_id;

        if (pnp->get_attr_uint32(node, SCSI_DEVICE_MAX_TARGET_COUNT, &bus->max_target_count, true) != B_OK)
                bus->max_target_count = MAX_TARGET_ID + 1;
        if (pnp->get_attr_uint32(node, SCSI_DEVICE_MAX_LUN_COUNT, &bus->max_lun_count, true) != B_OK)
                bus->max_lun_count = MAX_LUN_ID + 1;

        // our scsi_ccb only has a uchar for target_id
        if (bus->max_target_count > 256)
                bus->max_target_count = 256;
        // our scsi_ccb only has a uchar for target_lun
        if (bus->max_lun_count > 256)
                bus->max_lun_count = 256;

        bus->node = node;
        bus->lock_count = bus->blocked[0] = bus->blocked[1] = 0;
        bus->sim_overflow = 0;
        bus->shutting_down = false;

        bus->waiting_devices = NULL;
        //bus->resubmitted_req = NULL;

        bus->dpc_list = NULL;

        if ((bus->scan_lun_lock = create_sem(1, "scsi_scan_lun_lock")) < 0) {
                res = bus->scan_lun_lock;
                goto err6;
        }

        bus->start_service = create_sem(0, "scsi_start_service");
        if (bus->start_service < 0) {
                res = bus->start_service;
                goto err4;
        }

        mutex_init(&bus->mutex, "scsi_bus_mutex");
        spinlock_irq_init(&bus->dpc_lock);

        bus->service_thread = spawn_kernel_thread(scsi_service_threadproc,
                "scsi_bus_service", BUS_SERVICE_PRIORITY, bus);

        if (bus->service_thread < 0) {
                res = bus->service_thread;
                goto err1;
        }

        resume_thread(bus->service_thread);

        return bus;

err1:
        mutex_destroy(&bus->mutex);
        delete_sem(bus->start_service);
err4:
        delete_sem(bus->scan_lun_lock);
err6:
        free(bus);
        return NULL;
}


static status_t
scsi_destroy_bus(scsi_bus_info *bus)
{
        // noone is using this bus now, time to clean it up
        bus->shutting_down = true;
        release_sem(bus->start_service);

        status_t retcode;
        wait_for_thread(bus->service_thread, &retcode);

        delete_sem(bus->start_service);
        mutex_destroy(&bus->mutex);
        delete_sem(bus->scan_lun_lock);

        return B_OK;
}


static status_t
scsi_init_bus(device_node *node, void **cookie)
{
        uint8 path_id;
        scsi_bus_info *bus;
        status_t res;

        SHOW_FLOW0( 3, "" );

        if (pnp->get_attr_uint8(node, SCSI_BUS_PATH_ID_ITEM, &path_id, false) != B_OK)
                return B_ERROR;

        bus = scsi_create_bus(node, path_id);
        if (bus == NULL)
                return B_NO_MEMORY;

        // extract controller/protocoll restrictions from node
        if (pnp->get_attr_uint32(node, B_DMA_ALIGNMENT, &bus->dma_params.alignment,
                        true) != B_OK)
                bus->dma_params.alignment = 0;
        if (pnp->get_attr_uint32(node, B_DMA_MAX_TRANSFER_BLOCKS,
                        &bus->dma_params.max_blocks, true) != B_OK)
                bus->dma_params.max_blocks = UINT32_MAX;
        if (pnp->get_attr_uint32(node, B_DMA_BOUNDARY,
                        &bus->dma_params.dma_boundary, true) != B_OK)
                bus->dma_params.dma_boundary = UINT32_MAX;
        if (pnp->get_attr_uint32(node, B_DMA_MAX_SEGMENT_BLOCKS,
                        &bus->dma_params.max_sg_block_size, true) != B_OK)
                bus->dma_params.max_sg_block_size = UINT32_MAX;
        if (pnp->get_attr_uint32(node, B_DMA_MAX_SEGMENT_COUNT,
                        &bus->dma_params.max_sg_blocks, true) != B_OK)
                bus->dma_params.max_sg_blocks = UINT32_MAX;
        if (pnp->get_attr_uint64(node, B_DMA_HIGH_ADDRESS,
                        &bus->dma_params.high_address, true) != B_OK)
                bus->dma_params.high_address = UINT64_MAX;

        // do some sanity check:
        bus->dma_params.max_sg_block_size &= ~bus->dma_params.alignment;

        if (bus->dma_params.alignment > B_PAGE_SIZE) {
                SHOW_ERROR(0, "Alignment (0x%" B_PRIx32 ") must be less then "
                        "B_PAGE_SIZE", bus->dma_params.alignment);
                res = B_ERROR;
                goto err;
        }

        if (bus->dma_params.max_sg_block_size < 1) {
                SHOW_ERROR(0, "Max s/g block size (0x%" B_PRIx32 ") is too small",
                        bus->dma_params.max_sg_block_size);
                res = B_ERROR;
                goto err;
        }

        if (bus->dma_params.dma_boundary < B_PAGE_SIZE - 1) {
                SHOW_ERROR(0, "DMA boundary (0x%" B_PRIx32 ") must be at least "
                        "B_PAGE_SIZE", bus->dma_params.dma_boundary);
                res = B_ERROR;
                goto err;
        }

        if (bus->dma_params.max_blocks < 1 || bus->dma_params.max_sg_blocks < 1) {
                SHOW_ERROR(0, "Max blocks (%" B_PRIu32 ") and max s/g blocks (%"
                        B_PRIu32 ") must be at least 1", bus->dma_params.max_blocks,
                        bus->dma_params.max_sg_blocks);
                res = B_ERROR;
                goto err;
        }

        {
                device_node *parent = pnp->get_parent_node(node);
                pnp->get_driver(parent, (driver_module_info **)&bus->interface,
                        (void **)&bus->sim_cookie);
                pnp->put_node(parent);

                bus->interface->set_scsi_bus(bus->sim_cookie, bus);
        }

        // cache inquiry data
        scsi_inquiry_path(bus, &bus->inquiry_data);

        // get max. number of commands on bus
        bus->left_slots = bus->inquiry_data.hba_queue_size;
        SHOW_FLOW( 3, "Bus has %d slots", bus->left_slots );

        *cookie = bus;

        return B_OK;

err:
        scsi_destroy_bus(bus);
        return res;
}


static void
scsi_uninit_bus(scsi_bus_info *bus)
{
        scsi_destroy_bus(bus);
}


uchar
scsi_inquiry_path(scsi_bus bus, scsi_path_inquiry *inquiry_data)
{
        SHOW_FLOW(4, "path_id=%d", bus->path_id);
        return bus->interface->path_inquiry(bus->sim_cookie, inquiry_data);
}


static uchar
scsi_reset_bus(scsi_bus_info *bus)
{
        return bus->interface->reset_bus(bus->sim_cookie);
}


static status_t
scsi_bus_module_init(void)
{
        SHOW_FLOW0(4, "");

        status_t status = init_ccb_alloc();
        if (status != B_OK)
                return status;
        return init_temp_sg();
}


static status_t
scsi_bus_module_uninit(void)
{
        SHOW_INFO0(4, "");

        uninit_ccb_alloc();
        uninit_temp_sg();
        return B_OK;
}


static status_t
std_ops(int32 op, ...)
{
        switch (op) {
                case B_MODULE_INIT:
                        return scsi_bus_module_init();
                case B_MODULE_UNINIT:
                        return scsi_bus_module_uninit();

                default:
                        return B_ERROR;
        }
}


scsi_bus_interface scsi_bus_module = {
        {
                {
                        SCSI_BUS_MODULE_NAME,
                        0,
                        std_ops
                },

                NULL,   // supported devices
                NULL,   // register node
                scsi_init_bus,
                (void (*)(void *))scsi_uninit_bus,
                (status_t (*)(void *))scsi_scan_bus,
                (status_t (*)(void *))scsi_scan_bus,
                NULL
        },

        scsi_inquiry_path,
        scsi_reset_bus,
};