freebsd-nq/sys/dev/tws/tws_user.c
Pedro F. Giffuni 7282444b10 sys/dev: further adoption of SPDX licensing ID tags.
Mainly focus on files that use BSD 3-Clause license.

The Software Package Data Exchange (SPDX) group provides a specification
to make it easier for automated tools to detect and summarize well known
opensource licenses. We are gradually adopting the specification, noting
that the tags are considered only advisory and do not, in any way,
superceed or replace the license texts.

Special thanks to Wind River for providing access to "The Duke of
Highlander" tool: an older (2014) run over FreeBSD tree was useful as a
starting point.
2017-11-20 19:36:21 +00:00

397 lines
13 KiB
C

/*-
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2010, LSI Corp.
* All rights reserved.
* Author : Manjunath Ranganathaiah
* Support: freebsdraid@lsi.com
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name of the <ORGANIZATION> nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <dev/tws/tws.h>
#include <dev/tws/tws_services.h>
#include <dev/tws/tws_hdm.h>
#include <dev/tws/tws_user.h>
int tws_ioctl(struct cdev *dev, long unsigned int cmd, caddr_t buf, int flags,
struct thread *td);
void tws_passthru_complete(struct tws_request *req);
extern void tws_circular_aenq_insert(struct tws_softc *sc,
struct tws_circular_q *cq, struct tws_event_packet *aen);
static int tws_passthru(struct tws_softc *sc, void *buf);
static int tws_ioctl_aen(struct tws_softc *sc, u_long cmd, void *buf);
extern int tws_bus_scan(struct tws_softc *sc);
extern struct tws_request *tws_get_request(struct tws_softc *sc,
u_int16_t type);
extern int32_t tws_map_request(struct tws_softc *sc, struct tws_request *req);
extern void tws_unmap_request(struct tws_softc *sc, struct tws_request *req);
extern uint8_t tws_get_state(struct tws_softc *sc);
extern void tws_timeout(void *arg);
int
tws_ioctl(struct cdev *dev, u_long cmd, caddr_t buf, int flags,
struct thread *td)
{
struct tws_softc *sc = (struct tws_softc *)(dev->si_drv1);
int error;
TWS_TRACE_DEBUG(sc, "entry", sc, cmd);
sc->stats.ioctls++;
switch(cmd) {
case TWS_IOCTL_FIRMWARE_PASS_THROUGH :
error = tws_passthru(sc, (void *)buf);
break;
case TWS_IOCTL_SCAN_BUS :
TWS_TRACE_DEBUG(sc, "scan-bus", 0, 0);
error = tws_bus_scan(sc);
break;
default :
TWS_TRACE_DEBUG(sc, "ioctl-aen", cmd, buf);
error = tws_ioctl_aen(sc, cmd, (void *)buf);
break;
}
return(error);
}
static int
tws_passthru(struct tws_softc *sc, void *buf)
{
struct tws_request *req;
struct tws_ioctl_no_data_buf *ubuf = (struct tws_ioctl_no_data_buf *)buf;
int error;
u_int16_t lun4;
if ( tws_get_state(sc) != TWS_ONLINE) {
return(EBUSY);
}
//==============================================================================================
// Get a command
//
do {
req = tws_get_request(sc, TWS_REQ_TYPE_PASSTHRU);
if ( !req ) {
error = tsleep(sc, 0, "tws_sleep", TWS_IOCTL_TIMEOUT*hz);
if ( error == EWOULDBLOCK ) {
return(ETIMEDOUT);
}
} else {
// Make sure we are still ready for new commands...
if ( tws_get_state(sc) != TWS_ONLINE) {
return(EBUSY);
}
break;
}
} while(1);
req->length = (ubuf->driver_pkt.buffer_length + 511) & ~511;
TWS_TRACE_DEBUG(sc, "datal,rid", req->length, req->request_id);
if ( req->length ) {
req->data = sc->ioctl_data_mem;
req->dma_map = sc->ioctl_data_map;
//==========================================================================================
// Copy data in from user space
//
error = copyin(ubuf->pdata, req->data, req->length);
}
//==============================================================================================
// Set command fields
//
req->flags = TWS_DIR_IN | TWS_DIR_OUT;
req->cb = tws_passthru_complete;
memcpy(&req->cmd_pkt->cmd, &ubuf->cmd_pkt.cmd,
sizeof(struct tws_command_apache));
if ( GET_OPCODE(req->cmd_pkt->cmd.pkt_a.res__opcode) ==
TWS_FW_CMD_EXECUTE_SCSI ) {
lun4 = req->cmd_pkt->cmd.pkt_a.lun_l4__req_id & 0xF000;
req->cmd_pkt->cmd.pkt_a.lun_l4__req_id = lun4 | req->request_id;
} else {
req->cmd_pkt->cmd.pkt_g.generic.request_id = (u_int8_t) req->request_id;
}
//==============================================================================================
// Send command to controller
//
error = tws_map_request(sc, req);
if (error) {
ubuf->driver_pkt.os_status = error;
goto out_data;
}
if ( req->state == TWS_REQ_STATE_COMPLETE ) {
ubuf->driver_pkt.os_status = req->error_code;
goto out_unmap;
}
mtx_lock(&sc->gen_lock);
error = mtx_sleep(req, &sc->gen_lock, 0, "tws_passthru", TWS_IOCTL_TIMEOUT*hz);
mtx_unlock(&sc->gen_lock);
if (( req->state != TWS_REQ_STATE_COMPLETE ) && ( error == EWOULDBLOCK )) {
TWS_TRACE_DEBUG(sc, "msleep timeout", error, req->request_id);
tws_timeout((void*) req);
}
out_unmap:
if ( req->error_code == TWS_REQ_RET_RESET ) {
error = EBUSY;
req->error_code = EBUSY;
TWS_TRACE_DEBUG(sc, "ioctl reset", error, req->request_id);
}
tws_unmap_request(sc, req);
//==============================================================================================
// Return command status to user space
//
memcpy(&ubuf->cmd_pkt.hdr, &req->cmd_pkt->hdr, sizeof(struct tws_command_apache));
memcpy(&ubuf->cmd_pkt.cmd, &req->cmd_pkt->cmd, sizeof(struct tws_command_apache));
out_data:
if ( req->length ) {
//==========================================================================================
// Copy data out to user space
//
if ( !error )
error = copyout(req->data, ubuf->pdata, ubuf->driver_pkt.buffer_length);
}
if ( error )
TWS_TRACE_DEBUG(sc, "errored", error, 0);
if ( req->error_code != TWS_REQ_RET_SUBMIT_SUCCESS )
ubuf->driver_pkt.os_status = error;
//==============================================================================================
// Free command
//
req->state = TWS_REQ_STATE_FREE;
wakeup_one(sc);
return(error);
}
void
tws_passthru_complete(struct tws_request *req)
{
req->state = TWS_REQ_STATE_COMPLETE;
wakeup_one(req);
}
static void
tws_retrive_aen(struct tws_softc *sc, u_long cmd,
struct tws_ioctl_packet *ubuf)
{
u_int16_t index=0;
struct tws_event_packet eventp, *qp;
if ( sc->aen_q.head == sc->aen_q.tail ) {
ubuf->driver_pkt.status = TWS_AEN_NO_EVENTS;
return;
}
ubuf->driver_pkt.status = 0;
/*
* once this flag is set cli will not display alarms
* needs a revisit from tools?
*/
if ( sc->aen_q.overflow ) {
ubuf->driver_pkt.status = TWS_AEN_OVERFLOW;
sc->aen_q.overflow = 0; /* reset */
}
qp = (struct tws_event_packet *)sc->aen_q.q;
switch (cmd) {
case TWS_IOCTL_GET_FIRST_EVENT :
index = sc->aen_q.head;
break;
case TWS_IOCTL_GET_LAST_EVENT :
/* index = tail-1 */
index = (sc->aen_q.depth + sc->aen_q.tail - 1) % sc->aen_q.depth;
break;
case TWS_IOCTL_GET_NEXT_EVENT :
memcpy(&eventp, ubuf->data_buf, sizeof(struct tws_event_packet));
index = sc->aen_q.head;
do {
if ( qp[index].sequence_id ==
(eventp.sequence_id + 1) )
break;
index = (index+1) % sc->aen_q.depth;
}while ( index != sc->aen_q.tail );
if ( index == sc->aen_q.tail ) {
ubuf->driver_pkt.status = TWS_AEN_NO_EVENTS;
return;
}
break;
case TWS_IOCTL_GET_PREVIOUS_EVENT :
memcpy(&eventp, ubuf->data_buf, sizeof(struct tws_event_packet));
index = sc->aen_q.head;
do {
if ( qp[index].sequence_id ==
(eventp.sequence_id - 1) )
break;
index = (index+1) % sc->aen_q.depth;
}while ( index != sc->aen_q.tail );
if ( index == sc->aen_q.tail ) {
ubuf->driver_pkt.status = TWS_AEN_NO_EVENTS;
return;
}
break;
default :
TWS_TRACE_DEBUG(sc, "not a valid event", sc, cmd);
ubuf->driver_pkt.status = TWS_AEN_NO_EVENTS;
return;
}
memcpy(ubuf->data_buf, &qp[index],
sizeof(struct tws_event_packet));
qp[index].retrieved = TWS_AEN_RETRIEVED;
return;
}
static int
tws_ioctl_aen(struct tws_softc *sc, u_long cmd, void *buf)
{
struct tws_ioctl_packet *ubuf = (struct tws_ioctl_packet *)buf;
struct tws_compatibility_packet cpkt;
struct tws_lock_packet lpkt;
time_t ctime;
mtx_lock(&sc->gen_lock);
ubuf->driver_pkt.status = 0;
switch(cmd) {
case TWS_IOCTL_GET_FIRST_EVENT :
case TWS_IOCTL_GET_LAST_EVENT :
case TWS_IOCTL_GET_NEXT_EVENT :
case TWS_IOCTL_GET_PREVIOUS_EVENT :
tws_retrive_aen(sc,cmd,ubuf);
break;
case TWS_IOCTL_GET_LOCK :
ctime = TWS_LOCAL_TIME;
memcpy(&lpkt, ubuf->data_buf, sizeof(struct tws_lock_packet));
if ( (sc->ioctl_lock.lock == TWS_IOCTL_LOCK_FREE) ||
(lpkt.force_flag) ||
(ctime >= sc->ioctl_lock.timeout) ) {
sc->ioctl_lock.lock = TWS_IOCTL_LOCK_HELD;
sc->ioctl_lock.timeout = ctime + (lpkt.timeout_msec / 1000);
lpkt.time_remaining_msec = lpkt.timeout_msec;
} else {
lpkt.time_remaining_msec = (u_int32_t)
((sc->ioctl_lock.timeout - ctime) * 1000);
ubuf->driver_pkt.status = TWS_IOCTL_LOCK_ALREADY_HELD;
}
break;
case TWS_IOCTL_RELEASE_LOCK :
if (sc->ioctl_lock.lock == TWS_IOCTL_LOCK_FREE) {
ubuf->driver_pkt.status = TWS_IOCTL_LOCK_NOT_HELD;
} else {
sc->ioctl_lock.lock = TWS_IOCTL_LOCK_FREE;
ubuf->driver_pkt.status = 0;
}
break;
case TWS_IOCTL_GET_COMPATIBILITY_INFO :
TWS_TRACE_DEBUG(sc, "get comp info", sc, cmd);
memcpy( cpkt.driver_version, TWS_DRIVER_VERSION_STRING,
sizeof(TWS_DRIVER_VERSION_STRING));
cpkt.working_srl = sc->cinfo.working_srl;
cpkt.working_branch = sc->cinfo.working_branch;
cpkt.working_build = sc->cinfo.working_build;
cpkt.driver_srl_high = TWS_CURRENT_FW_SRL;
cpkt.driver_branch_high = TWS_CURRENT_FW_BRANCH;
cpkt.driver_build_high = TWS_CURRENT_FW_BUILD;
cpkt.driver_srl_low = TWS_BASE_FW_SRL;
cpkt.driver_branch_low = TWS_BASE_FW_BRANCH;
cpkt.driver_build_low = TWS_BASE_FW_BUILD;
cpkt.fw_on_ctlr_srl = sc->cinfo.fw_on_ctlr_srl;
cpkt.fw_on_ctlr_branch = sc->cinfo.fw_on_ctlr_branch;
cpkt.fw_on_ctlr_build = sc->cinfo.fw_on_ctlr_build;
ubuf->driver_pkt.status = 0;
int len = sizeof(struct tws_compatibility_packet);
if ( ubuf->driver_pkt.buffer_length < len )
len = ubuf->driver_pkt.buffer_length;
memcpy(ubuf->data_buf, &cpkt, len);
break;
default :
TWS_TRACE_DEBUG(sc, "not valid cmd", cmd,
TWS_IOCTL_GET_COMPATIBILITY_INFO);
break;
}
mtx_unlock(&sc->gen_lock);
return(SUCCESS);
}
void
tws_circular_aenq_insert(struct tws_softc *sc, struct tws_circular_q *cq,
struct tws_event_packet *aen)
{
struct tws_event_packet *q = (struct tws_event_packet *)cq->q;
volatile u_int16_t head, tail;
u_int8_t retr;
mtx_assert(&sc->gen_lock, MA_OWNED);
head = cq->head;
tail = cq->tail;
retr = q[tail].retrieved;
memcpy(&q[tail], aen, sizeof(struct tws_event_packet));
tail = (tail+1) % cq->depth;
if ( head == tail ) { /* q is full */
if ( retr != TWS_AEN_RETRIEVED )
cq->overflow = 1;
cq->head = (head+1) % cq->depth;
}
cq->tail = tail;
}