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

/*
        Device node layer.

        When a SCSI bus is registered, this layer scans for SCSI devices
        and registers a node for each of them. Peripheral drivers are on
        top of these nodes.
*/

#include "scsi_internal.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include <algorithm>


/** free autosense request of device */

static void
scsi_free_autosense_request(scsi_device_info *device)
{
        SHOW_FLOW0( 3, "" );

        if (device->auto_sense_request != NULL) {
                scsi_free_ccb(device->auto_sense_request);
                device->auto_sense_request = NULL;
        }

        if (device->auto_sense_area > 0) {
                delete_area(device->auto_sense_area);
                device->auto_sense_area = 0;
        }
}


/** free all data of device */

static void
scsi_free_device(scsi_device_info *device)
{
        SHOW_FLOW0( 3, "" );

        scsi_free_emulation_buffer(device);
        scsi_free_autosense_request(device);

        unregister_kernel_daemon(scsi_dma_buffer_daemon, device);

        scsi_dma_buffer_free(&device->dma_buffer);

        mutex_destroy(&device->dma_buffer_lock);
        delete_sem(device->dma_buffer_owner);

        free(device);
}


/**     copy string src without trailing zero to dst and remove trailing
 *      spaces size of dst is dst_size, size of src is dst_size-1
 */

static void
beautify_string(char *dst, char *src, int dst_size)
{
        int i;

        memcpy(dst, src, dst_size - 1);

        for (i = dst_size - 2; i >= 0; --i) {
                if (dst[i] != ' ')
                        break;
        }

        dst[i + 1] = 0;
}


/** register new device */

status_t
scsi_register_device(scsi_bus_info *bus, uchar target_id,
        uchar target_lun, scsi_res_inquiry *inquiry_data)
{
        bool is_atapi, manual_autosense;
        uint32 orig_max_blocks, max_blocks;

        SHOW_FLOW0( 3, "" );

        // ask for restrictions
        bus->interface->get_restrictions(bus->sim_cookie,
                target_id, &is_atapi, &manual_autosense, &max_blocks);
        if (target_lun != 0)
                dprintf("WARNING: SCSI target %d lun %d getting restrictions without lun\n",
                        target_id, target_lun);

        // find maximum transfer blocks
        // set default value to max (need something like ULONG_MAX here)
        orig_max_blocks = ~0;
        pnp->get_attr_uint32(bus->node, B_DMA_MAX_TRANSFER_BLOCKS, &orig_max_blocks,
                true);

        max_blocks = std::min(max_blocks, orig_max_blocks);

        {
                char vendor_ident[sizeof( inquiry_data->vendor_ident ) + 1];
                char product_ident[sizeof( inquiry_data->product_ident ) + 1];
                char product_rev[sizeof( inquiry_data->product_rev ) + 1];
                device_attr attrs[] = {
                        // connection
                        { SCSI_DEVICE_TARGET_ID_ITEM, B_UINT8_TYPE, { .ui8 = target_id }},
                        { SCSI_DEVICE_TARGET_LUN_ITEM, B_UINT8_TYPE, { .ui8 = target_lun }},

                        // inquiry data (used for both identification and information)
                        { SCSI_DEVICE_INQUIRY_ITEM, B_RAW_TYPE,
                                { .raw = { inquiry_data, sizeof( *inquiry_data ) }}},

                        // some more info for driver loading
                        { SCSI_DEVICE_TYPE_ITEM, B_UINT8_TYPE, { .ui8 = inquiry_data->device_type }},
                        { SCSI_DEVICE_VENDOR_ITEM, B_STRING_TYPE, { .string = vendor_ident }},
                        { SCSI_DEVICE_PRODUCT_ITEM, B_STRING_TYPE, { .string = product_ident }},
                        { SCSI_DEVICE_REVISION_ITEM, B_STRING_TYPE, { .string = product_rev }},

                        // description of peripheral drivers
                        { B_DEVICE_BUS, B_STRING_TYPE, { .string = "scsi" }},

                        // extra restriction of maximum number of blocks per transfer
                        { B_DMA_MAX_TRANSFER_BLOCKS, B_UINT32_TYPE, { .ui32 = max_blocks }},

                        // atapi emulation
                        { SCSI_DEVICE_IS_ATAPI_ITEM, B_UINT8_TYPE, { .ui8 = is_atapi }},
                        // manual autosense
                        { SCSI_DEVICE_MANUAL_AUTOSENSE_ITEM, B_UINT8_TYPE, { .ui8 = manual_autosense }},
                        { NULL }
                };

                beautify_string(vendor_ident, inquiry_data->vendor_ident, sizeof(vendor_ident));
                beautify_string(product_ident, inquiry_data->product_ident, sizeof(product_ident));
                beautify_string(product_rev, inquiry_data->product_rev, sizeof(product_rev));

                return pnp->register_node(bus->node, SCSI_DEVICE_MODULE_NAME, attrs,
                        NULL, NULL);
        }

        return B_OK;
}


// create data structure for a device
static scsi_device_info *
scsi_create_device(device_node *node, scsi_bus_info *bus,
        int target_id, int target_lun)
{
        scsi_device_info *device;

        SHOW_FLOW0( 3, "" );

        device = (scsi_device_info *)malloc(sizeof(*device));
        if (device == NULL)
                return NULL;

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

        device->lock_count = device->blocked[0] = device->blocked[1] = 0;
        device->sim_overflow = 0;
        device->queued_reqs = NULL;
        device->bus = bus;
        device->target_id = target_id;
        device->target_lun = target_lun;
        device->valid = true;
        device->node = node;

        scsi_dma_buffer_init(&device->dma_buffer);

        mutex_init(&device->dma_buffer_lock, "dma_buffer");

        device->dma_buffer_owner = create_sem(1, "dma_buffer");
        if (device->dma_buffer_owner < 0)
                goto err;

        register_kernel_daemon(scsi_dma_buffer_daemon, device, 5 * 10);

        return device;

err:
        mutex_destroy(&device->dma_buffer_lock);
        free(device);
        return NULL;
}


/**     prepare autosense request.
 *      this cannot be done on demand but during init as we may
 *      have run out of ccbs when we need it
 */

static status_t
scsi_create_autosense_request(scsi_device_info *device)
{
        scsi_ccb *request;
        unsigned char *buffer;
        scsi_cmd_request_sense *cmd;
        size_t total_size;

        SHOW_FLOW0( 3, "" );

        device->auto_sense_request = request = scsi_alloc_ccb(device);
        if (device->auto_sense_request == NULL)
                return B_NO_MEMORY;

        total_size = SCSI_MAX_SENSE_SIZE + sizeof(physical_entry);
        total_size = (total_size + B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1);

        // allocate buffer for space sense data and S/G list
        device->auto_sense_area = create_area("auto_sense", (void**)&buffer,
                B_ANY_KERNEL_ADDRESS, B_PAGE_SIZE, B_32_BIT_FULL_LOCK,
                B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
                // TODO: Use B_FULL_LOCK, if addresses >= 4 GB are supported!
        if (device->auto_sense_area < 0)
                goto err;

        request->data = buffer;
        request->data_length = SCSI_MAX_SENSE_SIZE;
        request->sg_list = (physical_entry *)(buffer + SCSI_MAX_SENSE_SIZE);
        request->sg_count = 1;

        get_memory_map(buffer, SCSI_MAX_SENSE_SIZE,
                (physical_entry *)request->sg_list, 1);

        // disable auto-autosense, just in case;
        // make sure no other request overtakes sense request;
        // buffer is/must be DMA safe as we cannot risk trouble with
        // dynamically allocated DMA buffer
        request->flags = SCSI_DIR_IN | SCSI_DIS_AUTOSENSE |
                SCSI_ORDERED_QTAG | SCSI_DMA_SAFE;

        cmd = (scsi_cmd_request_sense *)request->cdb;
        request->cdb_length = sizeof(*cmd);

        memset(cmd, 0, sizeof(*cmd));
        cmd->opcode = SCSI_OP_REQUEST_SENSE;
        cmd->lun = device->target_lun;
        cmd->allocation_length = SCSI_MAX_SENSE_SIZE;

        return B_OK;

err:
        scsi_free_ccb(request);
        return B_NO_MEMORY;
}


#define SET_BIT(field, bit) field[(bit) >> 3] |= 1 << ((bit) & 7)

static status_t
scsi_init_device(device_node *node, void **cookie)
{
        const scsi_res_inquiry *inquiry_data = NULL;
        uint8 target_id, target_lun, path_id;
        scsi_bus_info *bus;
        scsi_device_info *device;
        status_t res;
        size_t inquiry_data_len;
        uint8 is_atapi, manual_autosense;

        SHOW_FLOW0(3, "");

        if (pnp->get_attr_uint8( node, SCSI_DEVICE_TARGET_ID_ITEM, &target_id, false) != B_OK
                || pnp->get_attr_uint8( node, SCSI_DEVICE_TARGET_LUN_ITEM, &target_lun, false) != B_OK
                || pnp->get_attr_uint8( node, SCSI_DEVICE_IS_ATAPI_ITEM, &is_atapi, false) != B_OK
                || pnp->get_attr_uint8( node, SCSI_DEVICE_MANUAL_AUTOSENSE_ITEM, &manual_autosense, false) != B_OK
                || pnp->get_attr_raw( node, SCSI_DEVICE_INQUIRY_ITEM,
                                (const void **)&inquiry_data, &inquiry_data_len, false) != B_OK
                || inquiry_data_len != sizeof(*inquiry_data)) {
                return B_ERROR;
        }

        {
                device_node *parent = pnp->get_parent_node(node);
                pnp->get_driver(parent, NULL, (void **)&bus);
                pnp->put_node(parent);
        }

        device = scsi_create_device(node, bus, target_id, target_lun);
        if (device == NULL)
                return B_NO_MEMORY;

        // never mind if there is no path - it might be an emulated controller
        path_id = (uint8)-1;

        pnp->get_attr_uint8(node, SCSI_BUS_PATH_ID_ITEM, &path_id, true);

        device->inquiry_data = *inquiry_data;

        // save restrictions
        device->is_atapi = is_atapi;
        device->manual_autosense = manual_autosense;

        // size of device queue must be detected by trial and error, so
        // we start with a really high number and see when the device chokes
        device->total_slots = 4096;

        // disable queuing if bus doesn't support it
        if ((bus->inquiry_data.hba_inquiry & SCSI_PI_TAG_ABLE) == 0)
                device->total_slots = 1;

        // if there is no autosense, disable queuing to make sure autosense is
        // not overtaken by other requests
        if (device->manual_autosense)
                device->total_slots = 1;

        device->left_slots = device->total_slots;

        // get autosense request if required
        if (device->manual_autosense) {
                if (scsi_create_autosense_request(device) != B_OK) {
                        res = B_NO_MEMORY;
                        goto err;
                }
        }

        // if this is an ATAPI device, we need an emulation buffer
        if (scsi_init_emulation_buffer(device, SCSI_ATAPI_BUFFER_SIZE) != B_OK) {
                res = B_NO_MEMORY;
                goto err;
        }

        memset(device->emulation_map, 0, sizeof(device->emulation_map));

        if (device->is_atapi) {
                SET_BIT(device->emulation_map, SCSI_OP_READ_6);
                SET_BIT(device->emulation_map, SCSI_OP_WRITE_6);
                SET_BIT(device->emulation_map, SCSI_OP_MODE_SENSE_6);
                SET_BIT(device->emulation_map, SCSI_OP_MODE_SELECT_6);
                SET_BIT(device->emulation_map, SCSI_OP_INQUIRY);
        }

        *cookie = device;
        return B_OK;

err:
        scsi_free_device(device);
        return res;
}


static void
scsi_uninit_device(scsi_device_info *device)
{
        SHOW_FLOW0(3, "");

        scsi_free_device(device);
}


static void
scsi_device_removed(scsi_device_info *device)
{
        SHOW_FLOW0(3, "");

        if (device == NULL)
                return;

        // this must be atomic as no lock is used
        device->valid = false;
}


/**     get device info; create a temporary one if it's not registered
 *      (used during detection)
 *      on success, scan_lun_lock of bus is hold
 */

status_t
scsi_force_get_device(scsi_bus_info *bus, uchar target_id,
        uchar target_lun, scsi_device_info **res_device)
{
        device_attr attrs[] = {
                { SCSI_DEVICE_TARGET_ID_ITEM, B_UINT8_TYPE, { .ui8 = target_id }},
                { SCSI_DEVICE_TARGET_LUN_ITEM, B_UINT8_TYPE, { .ui8 = target_lun }},
                { NULL }
        };
        device_node *node;
        status_t res;
        driver_module_info *driver_interface;
        scsi_device device;

        SHOW_FLOW0(3, "");

        // very important: only one can use a forced device to avoid double detection
        acquire_sem(bus->scan_lun_lock);

        // check whether device registered already
        node = NULL;
        pnp->get_next_child_node(bus->node, attrs, &node);

        SHOW_FLOW(3, "%p", node);

        if (node != NULL) {
                // TODO: have a second look a this one!
                // there is one - get it
                res = pnp->get_driver(node, &driver_interface, (void **)&device);
                if (res != B_OK)
                        pnp->put_node(node);
        } else {
                // device doesn't exist yet - create a temporary one
                device = scsi_create_device(NULL, bus, target_id, target_lun);
                if (device == NULL)
                        res = B_NO_MEMORY;
                else
                        res = B_OK;
        }

        *res_device = device;

        if (res != B_OK)
                release_sem(bus->scan_lun_lock);

        return res;
}


/**     cleanup device received from scsi_force_get_device
 *      on return, scan_lun_lock of bus is released
 */

void
scsi_put_forced_device(scsi_device_info *device)
{
        scsi_bus_info *bus = device->bus;

        SHOW_FLOW0(3, "");

        if (device->node != NULL) {
                // device is registered
                pnp->put_node(device->node);
        } else {
                // device is temporary
                scsi_free_device(device);
        }

        release_sem(bus->scan_lun_lock);
}


static uchar
scsi_reset_device(scsi_device_info *device)
{
        SHOW_FLOW0(3, "");

        if (device->node == NULL)
                return SCSI_DEV_NOT_THERE;

        return device->bus->interface->reset_device(device->bus->sim_cookie,
                device->target_id, device->target_lun);
}


static status_t
scsi_ioctl(scsi_device_info *device, uint32 op, void *buffer, size_t length)
{
        if (device->bus->interface->ioctl != NULL) {
                return device->bus->interface->ioctl(device->bus->sim_cookie,
                        device->target_id, op, buffer, length);
        }

        return B_DEV_INVALID_IOCTL;
}


static status_t
std_ops(int32 op, ...)
{
        switch (op) {
                case B_MODULE_INIT:
                {
                        // Link to SCSI bus.
                        // SCSI device driver must have SCSI bus loaded, but it calls its functions
                        // directly instead via official interface, so this pointer is never read.
                        module_info *dummy;
                        return get_module(SCSI_BUS_MODULE_NAME, &dummy);
                }
                case B_MODULE_UNINIT:
                        return put_module(SCSI_BUS_MODULE_NAME);

                default:
                        return B_ERROR;
        }
}


scsi_device_interface scsi_device_module = {
        {
                {
                        SCSI_DEVICE_MODULE_NAME,
                        0,
                        std_ops
                },

                NULL,   // supported devices
                NULL,   // register node
                scsi_init_device,
                (void (*)(void *)) scsi_uninit_device,
                NULL,   // register child devices
                NULL,   // rescan
                (void (*)(void *)) scsi_device_removed
        },

        scsi_alloc_ccb,
        scsi_free_ccb,

        scsi_async_io,
        scsi_sync_io,
        scsi_abort,
        scsi_reset_device,
        scsi_term_io,
        scsi_ioctl,
};