Index: projects/sendfile/sbin/ifconfig/ifgroup.c =================================================================== --- projects/sendfile/sbin/ifconfig/ifgroup.c (revision 274716) +++ projects/sendfile/sbin/ifconfig/ifgroup.c (revision 274717) @@ -1,186 +1,185 @@ /*- * Copyright (c) 2006 Max Laier. All rights reserved. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #ifndef lint static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include #include #include #include #include #include #include #include #include #include #include #include "ifconfig.h" /* ARGSUSED */ static void setifgroup(const char *group_name, int d, int s, const struct afswtch *rafp) { struct ifgroupreq ifgr; memset(&ifgr, 0, sizeof(ifgr)); strlcpy(ifgr.ifgr_name, name, IFNAMSIZ); if (group_name[0] && isdigit(group_name[strlen(group_name) - 1])) errx(1, "setifgroup: group names may not end in a digit"); if (strlcpy(ifgr.ifgr_group, group_name, IFNAMSIZ) >= IFNAMSIZ) errx(1, "setifgroup: group name too long"); if (ioctl(s, SIOCAIFGROUP, (caddr_t)&ifgr) == -1 && errno != EEXIST) err(1," SIOCAIFGROUP"); } /* ARGSUSED */ static void unsetifgroup(const char *group_name, int d, int s, const struct afswtch *rafp) { struct ifgroupreq ifgr; memset(&ifgr, 0, sizeof(ifgr)); strlcpy(ifgr.ifgr_name, name, IFNAMSIZ); if (group_name[0] && isdigit(group_name[strlen(group_name) - 1])) errx(1, "unsetifgroup: group names may not end in a digit"); if (strlcpy(ifgr.ifgr_group, group_name, IFNAMSIZ) >= IFNAMSIZ) errx(1, "unsetifgroup: group name too long"); if (ioctl(s, SIOCDIFGROUP, (caddr_t)&ifgr) == -1 && errno != ENOENT) err(1, "SIOCDIFGROUP"); } static void getifgroups(int s) { int len, cnt; struct ifgroupreq ifgr; struct ifg_req *ifg; - if (!verbose) - return; - memset(&ifgr, 0, sizeof(ifgr)); strlcpy(ifgr.ifgr_name, name, IFNAMSIZ); if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) { if (errno == EINVAL || errno == ENOTTY) return; else err(1, "SIOCGIFGROUP"); } len = ifgr.ifgr_len; ifgr.ifgr_groups = (struct ifg_req *)calloc(len / sizeof(struct ifg_req), sizeof(struct ifg_req)); if (ifgr.ifgr_groups == NULL) err(1, "getifgroups"); if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) err(1, "SIOCGIFGROUP"); cnt = 0; ifg = ifgr.ifgr_groups; for (; ifg && len >= sizeof(struct ifg_req); ifg++) { len -= sizeof(struct ifg_req); if (strcmp(ifg->ifgrq_group, "all")) { if (cnt == 0) printf("\tgroups: "); cnt++; printf("%s ", ifg->ifgrq_group); } } if (cnt) printf("\n"); + + free(ifgr.ifgr_groups); } static void printgroup(const char *groupname) { struct ifgroupreq ifgr; struct ifg_req *ifg; int len, cnt = 0; int s; s = socket(AF_LOCAL, SOCK_DGRAM, 0); if (s == -1) err(1, "socket(AF_LOCAL,SOCK_DGRAM)"); bzero(&ifgr, sizeof(ifgr)); strlcpy(ifgr.ifgr_name, groupname, sizeof(ifgr.ifgr_name)); if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1) { if (errno == EINVAL || errno == ENOTTY || errno == ENOENT) exit(0); else err(1, "SIOCGIFGMEMB"); } len = ifgr.ifgr_len; if ((ifgr.ifgr_groups = calloc(1, len)) == NULL) err(1, "printgroup"); if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1) err(1, "SIOCGIFGMEMB"); for (ifg = ifgr.ifgr_groups; ifg && len >= sizeof(struct ifg_req); ifg++) { len -= sizeof(struct ifg_req); printf("%s\n", ifg->ifgrq_member); cnt++; } free(ifgr.ifgr_groups); exit(0); } static struct cmd group_cmds[] = { DEF_CMD_ARG("group", setifgroup), DEF_CMD_ARG("-group", unsetifgroup), }; static struct afswtch af_group = { .af_name = "af_group", .af_af = AF_UNSPEC, .af_other_status = getifgroups, }; static struct option group_gopt = { "g:", "[-g groupname]", printgroup }; static __constructor void group_ctor(void) { #define N(a) (sizeof(a) / sizeof(a[0])) int i; for (i = 0; i < N(group_cmds); i++) cmd_register(&group_cmds[i]); af_register(&af_group); opt_register(&group_gopt); #undef N } Index: projects/sendfile/sbin =================================================================== --- projects/sendfile/sbin (revision 274716) +++ projects/sendfile/sbin (revision 274717) Property changes on: projects/sendfile/sbin ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head/sbin:r274690-274716 Index: projects/sendfile/sys/cam/ctl/ctl_frontend_iscsi.c =================================================================== --- projects/sendfile/sys/cam/ctl/ctl_frontend_iscsi.c (revision 274716) +++ projects/sendfile/sys/cam/ctl/ctl_frontend_iscsi.c (revision 274717) @@ -1,2931 +1,2931 @@ /*- * Copyright (c) 2012 The FreeBSD Foundation * All rights reserved. * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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$ */ /* * CTL frontend for the iSCSI protocol. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ICL_KERNEL_PROXY #include #endif #ifdef ICL_KERNEL_PROXY FEATURE(cfiscsi_kernel_proxy, "iSCSI target built with ICL_KERNEL_PROXY"); #endif static MALLOC_DEFINE(M_CFISCSI, "cfiscsi", "Memory used for CTL iSCSI frontend"); static uma_zone_t cfiscsi_data_wait_zone; SYSCTL_NODE(_kern_cam_ctl, OID_AUTO, iscsi, CTLFLAG_RD, 0, "CAM Target Layer iSCSI Frontend"); static int debug = 1; SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, debug, CTLFLAG_RWTUN, &debug, 1, "Enable debug messages"); static int ping_timeout = 5; SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, ping_timeout, CTLFLAG_RWTUN, &ping_timeout, 5, "Interval between ping (NOP-Out) requests, in seconds"); static int login_timeout = 60; SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, login_timeout, CTLFLAG_RWTUN, &login_timeout, 60, "Time to wait for ctld(8) to finish Login Phase, in seconds"); static int maxcmdsn_delta = 256; SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, maxcmdsn_delta, CTLFLAG_RWTUN, &maxcmdsn_delta, 256, "Number of commands the initiator can send " "without confirmation"); #define CFISCSI_DEBUG(X, ...) \ do { \ if (debug > 1) { \ printf("%s: " X "\n", \ __func__, ## __VA_ARGS__); \ } \ } while (0) #define CFISCSI_WARN(X, ...) \ do { \ if (debug > 0) { \ printf("WARNING: %s: " X "\n", \ __func__, ## __VA_ARGS__); \ } \ } while (0) #define CFISCSI_SESSION_DEBUG(S, X, ...) \ do { \ if (debug > 1) { \ printf("%s: %s (%s): " X "\n", \ __func__, S->cs_initiator_addr, \ S->cs_initiator_name, ## __VA_ARGS__); \ } \ } while (0) #define CFISCSI_SESSION_WARN(S, X, ...) \ do { \ if (debug > 0) { \ printf("WARNING: %s (%s): " X "\n", \ S->cs_initiator_addr, \ S->cs_initiator_name, ## __VA_ARGS__); \ } \ } while (0) #define CFISCSI_SESSION_LOCK(X) mtx_lock(&X->cs_lock) #define CFISCSI_SESSION_UNLOCK(X) mtx_unlock(&X->cs_lock) #define CFISCSI_SESSION_LOCK_ASSERT(X) mtx_assert(&X->cs_lock, MA_OWNED) #define CONN_SESSION(X) ((struct cfiscsi_session *)(X)->ic_prv0) #define PDU_SESSION(X) CONN_SESSION((X)->ip_conn) #define PDU_EXPDATASN(X) (X)->ip_prv0 #define PDU_TOTAL_TRANSFER_LEN(X) (X)->ip_prv1 #define PDU_R2TSN(X) (X)->ip_prv2 int cfiscsi_init(void); static void cfiscsi_online(void *arg); static void cfiscsi_offline(void *arg); static int cfiscsi_info(void *arg, struct sbuf *sb); static int cfiscsi_lun_enable(void *arg, struct ctl_id target_id, int lun_id); static int cfiscsi_lun_disable(void *arg, struct ctl_id target_id, int lun_id); static uint32_t cfiscsi_lun_map(void *arg, uint32_t lun); static int cfiscsi_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td); static void cfiscsi_datamove(union ctl_io *io); static void cfiscsi_datamove_in(union ctl_io *io); static void cfiscsi_datamove_out(union ctl_io *io); static void cfiscsi_done(union ctl_io *io); static bool cfiscsi_pdu_update_cmdsn(const struct icl_pdu *request); static void cfiscsi_pdu_handle_nop_out(struct icl_pdu *request); static void cfiscsi_pdu_handle_scsi_command(struct icl_pdu *request); static void cfiscsi_pdu_handle_task_request(struct icl_pdu *request); static void cfiscsi_pdu_handle_data_out(struct icl_pdu *request); static void cfiscsi_pdu_handle_logout_request(struct icl_pdu *request); static void cfiscsi_session_terminate(struct cfiscsi_session *cs); static struct cfiscsi_target *cfiscsi_target_find(struct cfiscsi_softc *softc, const char *name); static struct cfiscsi_target *cfiscsi_target_find_or_create( struct cfiscsi_softc *softc, const char *name, const char *alias); static void cfiscsi_target_release(struct cfiscsi_target *ct); static void cfiscsi_session_delete(struct cfiscsi_session *cs); static struct cfiscsi_softc cfiscsi_softc; extern struct ctl_softc *control_softc; static struct ctl_frontend cfiscsi_frontend = { .name = "iscsi", .init = cfiscsi_init, .ioctl = cfiscsi_ioctl, }; CTL_FRONTEND_DECLARE(ctlcfiscsi, cfiscsi_frontend); MODULE_DEPEND(ctlcfiscsi, icl, 1, 1, 1); static struct icl_pdu * cfiscsi_pdu_new_response(struct icl_pdu *request, int flags) { return (icl_pdu_new(request->ip_conn, flags)); } static bool cfiscsi_pdu_update_cmdsn(const struct icl_pdu *request) { const struct iscsi_bhs_scsi_command *bhssc; struct cfiscsi_session *cs; uint32_t cmdsn, expstatsn; cs = PDU_SESSION(request); /* * Every incoming PDU - not just NOP-Out - resets the ping timer. * The purpose of the timeout is to reset the connection when it stalls; * we don't want this to happen when NOP-In or NOP-Out ends up delayed * in some queue. * * XXX: Locking? */ cs->cs_timeout = 0; /* * Data-Out PDUs don't contain CmdSN. */ if ((request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == ISCSI_BHS_OPCODE_SCSI_DATA_OUT) return (false); /* * We're only using fields common for all the request * (initiator -> target) PDUs. */ bhssc = (const struct iscsi_bhs_scsi_command *)request->ip_bhs; cmdsn = ntohl(bhssc->bhssc_cmdsn); expstatsn = ntohl(bhssc->bhssc_expstatsn); CFISCSI_SESSION_LOCK(cs); #if 0 if (expstatsn != cs->cs_statsn) { CFISCSI_SESSION_DEBUG(cs, "received PDU with ExpStatSN %d, " "while current StatSN is %d", expstatsn, cs->cs_statsn); } #endif /* * The target MUST silently ignore any non-immediate command outside * of this range. */ if (cmdsn < cs->cs_cmdsn || cmdsn > cs->cs_cmdsn + maxcmdsn_delta) { CFISCSI_SESSION_UNLOCK(cs); CFISCSI_SESSION_WARN(cs, "received PDU with CmdSN %d, " "while expected CmdSN was %d", cmdsn, cs->cs_cmdsn); return (true); } if ((request->ip_bhs->bhs_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0) cs->cs_cmdsn++; CFISCSI_SESSION_UNLOCK(cs); return (false); } static void cfiscsi_pdu_handle(struct icl_pdu *request) { struct cfiscsi_session *cs; bool ignore; cs = PDU_SESSION(request); ignore = cfiscsi_pdu_update_cmdsn(request); if (ignore) { icl_pdu_free(request); return; } /* * Handle the PDU; this includes e.g. receiving the remaining * part of PDU and submitting the SCSI command to CTL * or queueing a reply. The handling routine is responsible * for freeing the PDU when it's no longer needed. */ switch (request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) { case ISCSI_BHS_OPCODE_NOP_OUT: cfiscsi_pdu_handle_nop_out(request); break; case ISCSI_BHS_OPCODE_SCSI_COMMAND: cfiscsi_pdu_handle_scsi_command(request); break; case ISCSI_BHS_OPCODE_TASK_REQUEST: cfiscsi_pdu_handle_task_request(request); break; case ISCSI_BHS_OPCODE_SCSI_DATA_OUT: cfiscsi_pdu_handle_data_out(request); break; case ISCSI_BHS_OPCODE_LOGOUT_REQUEST: cfiscsi_pdu_handle_logout_request(request); break; default: CFISCSI_SESSION_WARN(cs, "received PDU with unsupported " "opcode 0x%x; dropping connection", request->ip_bhs->bhs_opcode); icl_pdu_free(request); cfiscsi_session_terminate(cs); } } static void cfiscsi_receive_callback(struct icl_pdu *request) { struct cfiscsi_session *cs; cs = PDU_SESSION(request); #ifdef ICL_KERNEL_PROXY if (cs->cs_waiting_for_ctld || cs->cs_login_phase) { if (cs->cs_login_pdu == NULL) cs->cs_login_pdu = request; else icl_pdu_free(request); cv_signal(&cs->cs_login_cv); return; } #endif cfiscsi_pdu_handle(request); } static void cfiscsi_error_callback(struct icl_conn *ic) { struct cfiscsi_session *cs; cs = CONN_SESSION(ic); CFISCSI_SESSION_WARN(cs, "connection error; dropping connection"); cfiscsi_session_terminate(cs); } static int cfiscsi_pdu_prepare(struct icl_pdu *response) { struct cfiscsi_session *cs; struct iscsi_bhs_scsi_response *bhssr; bool advance_statsn = true; cs = PDU_SESSION(response); CFISCSI_SESSION_LOCK_ASSERT(cs); /* * We're only using fields common for all the response * (target -> initiator) PDUs. */ bhssr = (struct iscsi_bhs_scsi_response *)response->ip_bhs; /* * 10.8.3: "The StatSN for this connection is not advanced * after this PDU is sent." */ if (bhssr->bhssr_opcode == ISCSI_BHS_OPCODE_R2T) advance_statsn = false; /* * 10.19.2: "However, when the Initiator Task Tag is set to 0xffffffff, * StatSN for the connection is not advanced after this PDU is sent." */ if (bhssr->bhssr_opcode == ISCSI_BHS_OPCODE_NOP_IN && bhssr->bhssr_initiator_task_tag == 0xffffffff) advance_statsn = false; /* * See the comment below - StatSN is not meaningful and must * not be advanced. */ if (bhssr->bhssr_opcode == ISCSI_BHS_OPCODE_SCSI_DATA_IN) advance_statsn = false; /* * 10.7.3: "The fields StatSN, Status, and Residual Count * only have meaningful content if the S bit is set to 1." */ if (bhssr->bhssr_opcode != ISCSI_BHS_OPCODE_SCSI_DATA_IN) bhssr->bhssr_statsn = htonl(cs->cs_statsn); bhssr->bhssr_expcmdsn = htonl(cs->cs_cmdsn); bhssr->bhssr_maxcmdsn = htonl(cs->cs_cmdsn + maxcmdsn_delta); if (advance_statsn) cs->cs_statsn++; return (0); } static void cfiscsi_pdu_queue(struct icl_pdu *response) { struct cfiscsi_session *cs; cs = PDU_SESSION(response); CFISCSI_SESSION_LOCK(cs); cfiscsi_pdu_prepare(response); icl_pdu_queue(response); CFISCSI_SESSION_UNLOCK(cs); } static uint32_t cfiscsi_decode_lun(uint64_t encoded) { uint8_t lun[8]; uint32_t result; /* * The LUN field in iSCSI PDUs may look like an ordinary 64 bit number, * but is in fact an evil, multidimensional structure defined * in SCSI Architecture Model 5 (SAM-5), section 4.6. */ memcpy(lun, &encoded, sizeof(lun)); switch (lun[0] & 0xC0) { case 0x00: if ((lun[0] & 0x3f) != 0 || lun[2] != 0 || lun[3] != 0 || lun[4] != 0 || lun[5] != 0 || lun[6] != 0 || lun[7] != 0) { CFISCSI_WARN("malformed LUN " "(peripheral device addressing method): 0x%jx", (uintmax_t)encoded); result = 0xffffffff; break; } result = lun[1]; break; case 0x40: if (lun[2] != 0 || lun[3] != 0 || lun[4] != 0 || lun[5] != 0 || lun[6] != 0 || lun[7] != 0) { CFISCSI_WARN("malformed LUN " "(flat address space addressing method): 0x%jx", (uintmax_t)encoded); result = 0xffffffff; break; } result = ((lun[0] & 0x3f) << 8) + lun[1]; break; case 0xC0: if (lun[0] != 0xD2 || lun[4] != 0 || lun[5] != 0 || lun[6] != 0 || lun[7] != 0) { CFISCSI_WARN("malformed LUN (extended flat " "address space addressing method): 0x%jx", (uintmax_t)encoded); result = 0xffffffff; break; } result = (lun[1] << 16) + (lun[2] << 8) + lun[3]; default: CFISCSI_WARN("unsupported LUN format 0x%jx", (uintmax_t)encoded); result = 0xffffffff; break; } return (result); } static void cfiscsi_pdu_handle_nop_out(struct icl_pdu *request) { struct cfiscsi_session *cs; struct iscsi_bhs_nop_out *bhsno; struct iscsi_bhs_nop_in *bhsni; struct icl_pdu *response; void *data = NULL; size_t datasize; int error; cs = PDU_SESSION(request); bhsno = (struct iscsi_bhs_nop_out *)request->ip_bhs; if (bhsno->bhsno_initiator_task_tag == 0xffffffff) { /* * Nothing to do, iscsi_pdu_update_statsn() already * zeroed the timeout. */ icl_pdu_free(request); return; } datasize = icl_pdu_data_segment_length(request); if (datasize > 0) { data = malloc(datasize, M_CFISCSI, M_NOWAIT | M_ZERO); if (data == NULL) { CFISCSI_SESSION_WARN(cs, "failed to allocate memory; " "dropping connection"); icl_pdu_free(request); cfiscsi_session_terminate(cs); return; } icl_pdu_get_data(request, 0, data, datasize); } response = cfiscsi_pdu_new_response(request, M_NOWAIT); if (response == NULL) { CFISCSI_SESSION_WARN(cs, "failed to allocate memory; " "droppping connection"); free(data, M_CFISCSI); icl_pdu_free(request); cfiscsi_session_terminate(cs); return; } bhsni = (struct iscsi_bhs_nop_in *)response->ip_bhs; bhsni->bhsni_opcode = ISCSI_BHS_OPCODE_NOP_IN; bhsni->bhsni_flags = 0x80; bhsni->bhsni_initiator_task_tag = bhsno->bhsno_initiator_task_tag; bhsni->bhsni_target_transfer_tag = 0xffffffff; if (datasize > 0) { error = icl_pdu_append_data(response, data, datasize, M_NOWAIT); if (error != 0) { CFISCSI_SESSION_WARN(cs, "failed to allocate memory; " "dropping connection"); free(data, M_CFISCSI); icl_pdu_free(request); icl_pdu_free(response); cfiscsi_session_terminate(cs); return; } free(data, M_CFISCSI); } icl_pdu_free(request); cfiscsi_pdu_queue(response); } static void cfiscsi_pdu_handle_scsi_command(struct icl_pdu *request) { struct iscsi_bhs_scsi_command *bhssc; struct cfiscsi_session *cs; union ctl_io *io; int error; cs = PDU_SESSION(request); bhssc = (struct iscsi_bhs_scsi_command *)request->ip_bhs; //CFISCSI_SESSION_DEBUG(cs, "initiator task tag 0x%x", // bhssc->bhssc_initiator_task_tag); if (request->ip_data_len > 0 && cs->cs_immediate_data == false) { CFISCSI_SESSION_WARN(cs, "unsolicited data with " "ImmediateData=No; dropping connection"); icl_pdu_free(request); cfiscsi_session_terminate(cs); return; } io = ctl_alloc_io(cs->cs_target->ct_port.ctl_pool_ref); if (io == NULL) { CFISCSI_SESSION_WARN(cs, "can't allocate ctl_io; " "dropping connection"); icl_pdu_free(request); cfiscsi_session_terminate(cs); return; } ctl_zero_io(io); io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = request; io->io_hdr.io_type = CTL_IO_SCSI; io->io_hdr.nexus.initid.id = cs->cs_ctl_initid; io->io_hdr.nexus.targ_port = cs->cs_target->ct_port.targ_port; io->io_hdr.nexus.targ_target.id = 0; io->io_hdr.nexus.targ_lun = cfiscsi_decode_lun(bhssc->bhssc_lun); io->scsiio.tag_num = bhssc->bhssc_initiator_task_tag; switch ((bhssc->bhssc_flags & BHSSC_FLAGS_ATTR)) { case BHSSC_FLAGS_ATTR_UNTAGGED: io->scsiio.tag_type = CTL_TAG_UNTAGGED; break; case BHSSC_FLAGS_ATTR_SIMPLE: io->scsiio.tag_type = CTL_TAG_SIMPLE; break; case BHSSC_FLAGS_ATTR_ORDERED: io->scsiio.tag_type = CTL_TAG_ORDERED; break; case BHSSC_FLAGS_ATTR_HOQ: io->scsiio.tag_type = CTL_TAG_HEAD_OF_QUEUE; break; case BHSSC_FLAGS_ATTR_ACA: io->scsiio.tag_type = CTL_TAG_ACA; break; default: io->scsiio.tag_type = CTL_TAG_UNTAGGED; CFISCSI_SESSION_WARN(cs, "unhandled tag type %d", bhssc->bhssc_flags & BHSSC_FLAGS_ATTR); break; } io->scsiio.cdb_len = sizeof(bhssc->bhssc_cdb); /* Which is 16. */ memcpy(io->scsiio.cdb, bhssc->bhssc_cdb, sizeof(bhssc->bhssc_cdb)); refcount_acquire(&cs->cs_outstanding_ctl_pdus); error = ctl_queue(io); if (error != CTL_RETVAL_COMPLETE) { CFISCSI_SESSION_WARN(cs, "ctl_queue() failed; error %d; " "dropping connection", error); ctl_free_io(io); refcount_release(&cs->cs_outstanding_ctl_pdus); icl_pdu_free(request); cfiscsi_session_terminate(cs); } } static void cfiscsi_pdu_handle_task_request(struct icl_pdu *request) { struct iscsi_bhs_task_management_request *bhstmr; struct iscsi_bhs_task_management_response *bhstmr2; struct icl_pdu *response; struct cfiscsi_session *cs; union ctl_io *io; int error; cs = PDU_SESSION(request); bhstmr = (struct iscsi_bhs_task_management_request *)request->ip_bhs; io = ctl_alloc_io(cs->cs_target->ct_port.ctl_pool_ref); if (io == NULL) { CFISCSI_SESSION_WARN(cs, "can't allocate ctl_io;" "dropping connection"); icl_pdu_free(request); cfiscsi_session_terminate(cs); return; } ctl_zero_io(io); io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = request; io->io_hdr.io_type = CTL_IO_TASK; io->io_hdr.nexus.initid.id = cs->cs_ctl_initid; io->io_hdr.nexus.targ_port = cs->cs_target->ct_port.targ_port; io->io_hdr.nexus.targ_target.id = 0; io->io_hdr.nexus.targ_lun = cfiscsi_decode_lun(bhstmr->bhstmr_lun); io->taskio.tag_type = CTL_TAG_SIMPLE; /* XXX */ switch (bhstmr->bhstmr_function & ~0x80) { case BHSTMR_FUNCTION_ABORT_TASK: #if 0 CFISCSI_SESSION_DEBUG(cs, "BHSTMR_FUNCTION_ABORT_TASK"); #endif io->taskio.task_action = CTL_TASK_ABORT_TASK; io->taskio.tag_num = bhstmr->bhstmr_referenced_task_tag; break; case BHSTMR_FUNCTION_ABORT_TASK_SET: #if 0 CFISCSI_SESSION_DEBUG(cs, "BHSTMR_FUNCTION_ABORT_TASK_SET"); #endif io->taskio.task_action = CTL_TASK_ABORT_TASK_SET; break; case BHSTMR_FUNCTION_LOGICAL_UNIT_RESET: #if 0 CFISCSI_SESSION_DEBUG(cs, "BHSTMR_FUNCTION_LOGICAL_UNIT_RESET"); #endif io->taskio.task_action = CTL_TASK_LUN_RESET; break; case BHSTMR_FUNCTION_TARGET_WARM_RESET: #if 0 CFISCSI_SESSION_DEBUG(cs, "BHSTMR_FUNCTION_TARGET_WARM_RESET"); #endif io->taskio.task_action = CTL_TASK_TARGET_RESET; break; default: CFISCSI_SESSION_DEBUG(cs, "unsupported function 0x%x", bhstmr->bhstmr_function & ~0x80); ctl_free_io(io); response = cfiscsi_pdu_new_response(request, M_NOWAIT); if (response == NULL) { CFISCSI_SESSION_WARN(cs, "failed to allocate memory; " "dropping connection"); icl_pdu_free(request); cfiscsi_session_terminate(cs); return; } bhstmr2 = (struct iscsi_bhs_task_management_response *) response->ip_bhs; bhstmr2->bhstmr_opcode = ISCSI_BHS_OPCODE_TASK_RESPONSE; bhstmr2->bhstmr_flags = 0x80; bhstmr2->bhstmr_response = BHSTMR_RESPONSE_FUNCTION_NOT_SUPPORTED; bhstmr2->bhstmr_initiator_task_tag = bhstmr->bhstmr_initiator_task_tag; icl_pdu_free(request); cfiscsi_pdu_queue(response); return; } refcount_acquire(&cs->cs_outstanding_ctl_pdus); error = ctl_queue(io); if (error != CTL_RETVAL_COMPLETE) { CFISCSI_SESSION_WARN(cs, "ctl_queue() failed; error %d; " "dropping connection", error); ctl_free_io(io); refcount_release(&cs->cs_outstanding_ctl_pdus); icl_pdu_free(request); cfiscsi_session_terminate(cs); } } static bool cfiscsi_handle_data_segment(struct icl_pdu *request, struct cfiscsi_data_wait *cdw) { struct iscsi_bhs_data_out *bhsdo; struct cfiscsi_session *cs; struct ctl_sg_entry ctl_sg_entry, *ctl_sglist; size_t copy_len, len, off, buffer_offset; int ctl_sg_count; union ctl_io *io; cs = PDU_SESSION(request); KASSERT((request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == ISCSI_BHS_OPCODE_SCSI_DATA_OUT || (request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == ISCSI_BHS_OPCODE_SCSI_COMMAND, ("bad opcode 0x%x", request->ip_bhs->bhs_opcode)); /* * We're only using fields common for Data-Out and SCSI Command PDUs. */ bhsdo = (struct iscsi_bhs_data_out *)request->ip_bhs; io = cdw->cdw_ctl_io; KASSERT((io->io_hdr.flags & CTL_FLAG_DATA_MASK) != CTL_FLAG_DATA_IN, ("CTL_FLAG_DATA_IN")); #if 0 CFISCSI_SESSION_DEBUG(cs, "received %zd bytes out of %d", request->ip_data_len, io->scsiio.kern_total_len); #endif if (io->scsiio.kern_sg_entries > 0) { ctl_sglist = (struct ctl_sg_entry *)io->scsiio.kern_data_ptr; ctl_sg_count = io->scsiio.kern_sg_entries; } else { ctl_sglist = &ctl_sg_entry; ctl_sglist->addr = io->scsiio.kern_data_ptr; ctl_sglist->len = io->scsiio.kern_data_len; ctl_sg_count = 1; } if ((request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == ISCSI_BHS_OPCODE_SCSI_DATA_OUT) buffer_offset = ntohl(bhsdo->bhsdo_buffer_offset); else buffer_offset = 0; len = icl_pdu_data_segment_length(request); /* * Make sure the offset, as sent by the initiator, matches the offset * we're supposed to be at in the scatter-gather list. */ if (buffer_offset > io->scsiio.kern_rel_offset + io->scsiio.ext_data_filled || buffer_offset + len <= io->scsiio.kern_rel_offset + io->scsiio.ext_data_filled) { CFISCSI_SESSION_WARN(cs, "received bad buffer offset %zd, " "expected %zd; dropping connection", buffer_offset, (size_t)io->scsiio.kern_rel_offset + (size_t)io->scsiio.ext_data_filled); ctl_set_data_phase_error(&io->scsiio); cfiscsi_session_terminate(cs); return (true); } /* * This is the offset within the PDU data segment, as opposed * to buffer_offset, which is the offset within the task (SCSI * command). */ off = io->scsiio.kern_rel_offset + io->scsiio.ext_data_filled - buffer_offset; /* * Iterate over the scatter/gather segments, filling them with data * from the PDU data segment. Note that this can get called multiple * times for one SCSI command; the cdw structure holds state for the * scatter/gather list. */ for (;;) { KASSERT(cdw->cdw_sg_index < ctl_sg_count, ("cdw->cdw_sg_index >= ctl_sg_count")); if (cdw->cdw_sg_len == 0) { cdw->cdw_sg_addr = ctl_sglist[cdw->cdw_sg_index].addr; cdw->cdw_sg_len = ctl_sglist[cdw->cdw_sg_index].len; } KASSERT(off <= len, ("len > off")); copy_len = len - off; if (copy_len > cdw->cdw_sg_len) copy_len = cdw->cdw_sg_len; icl_pdu_get_data(request, off, cdw->cdw_sg_addr, copy_len); cdw->cdw_sg_addr += copy_len; cdw->cdw_sg_len -= copy_len; off += copy_len; io->scsiio.ext_data_filled += copy_len; if (cdw->cdw_sg_len == 0) { /* * End of current segment. */ if (cdw->cdw_sg_index == ctl_sg_count - 1) { /* * Last segment in scatter/gather list. */ break; } cdw->cdw_sg_index++; } if (off == len) { /* * End of PDU payload. */ break; } } if (len > off) { /* * In case of unsolicited data, it's possible that the buffer * provided by CTL is smaller than negotiated FirstBurstLength. * Just ignore the superfluous data; will ask for them with R2T * on next call to cfiscsi_datamove(). * * This obviously can only happen with SCSI Command PDU. */ if ((request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == ISCSI_BHS_OPCODE_SCSI_COMMAND) return (true); CFISCSI_SESSION_WARN(cs, "received too much data: got %zd bytes, " "expected %zd; dropping connection", icl_pdu_data_segment_length(request), off); ctl_set_data_phase_error(&io->scsiio); cfiscsi_session_terminate(cs); return (true); } if (io->scsiio.ext_data_filled == cdw->cdw_r2t_end && (bhsdo->bhsdo_flags & BHSDO_FLAGS_F) == 0) { CFISCSI_SESSION_WARN(cs, "got the final packet without " "the F flag; flags = 0x%x; dropping connection", bhsdo->bhsdo_flags); ctl_set_data_phase_error(&io->scsiio); cfiscsi_session_terminate(cs); return (true); } if (io->scsiio.ext_data_filled != cdw->cdw_r2t_end && (bhsdo->bhsdo_flags & BHSDO_FLAGS_F) != 0) { if ((request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == ISCSI_BHS_OPCODE_SCSI_DATA_OUT) { CFISCSI_SESSION_WARN(cs, "got the final packet, but the " "transmitted size was %zd bytes instead of %d; " "dropping connection", (size_t)io->scsiio.ext_data_filled, cdw->cdw_r2t_end); ctl_set_data_phase_error(&io->scsiio); cfiscsi_session_terminate(cs); return (true); } else { /* * For SCSI Command PDU, this just means we need to * solicit more data by sending R2T. */ return (false); } } if (io->scsiio.ext_data_filled == cdw->cdw_r2t_end) { #if 0 CFISCSI_SESSION_DEBUG(cs, "no longer expecting Data-Out with target " "transfer tag 0x%x", cdw->cdw_target_transfer_tag); #endif return (true); } return (false); } static void cfiscsi_pdu_handle_data_out(struct icl_pdu *request) { struct iscsi_bhs_data_out *bhsdo; struct cfiscsi_session *cs; struct cfiscsi_data_wait *cdw = NULL; union ctl_io *io; bool done; cs = PDU_SESSION(request); bhsdo = (struct iscsi_bhs_data_out *)request->ip_bhs; CFISCSI_SESSION_LOCK(cs); TAILQ_FOREACH(cdw, &cs->cs_waiting_for_data_out, cdw_next) { #if 0 CFISCSI_SESSION_DEBUG(cs, "have ttt 0x%x, itt 0x%x; looking for " "ttt 0x%x, itt 0x%x", bhsdo->bhsdo_target_transfer_tag, bhsdo->bhsdo_initiator_task_tag, cdw->cdw_target_transfer_tag, cdw->cdw_initiator_task_tag)); #endif if (bhsdo->bhsdo_target_transfer_tag == cdw->cdw_target_transfer_tag) break; } CFISCSI_SESSION_UNLOCK(cs); if (cdw == NULL) { CFISCSI_SESSION_WARN(cs, "data transfer tag 0x%x, initiator task tag " "0x%x, not found; dropping connection", bhsdo->bhsdo_target_transfer_tag, bhsdo->bhsdo_initiator_task_tag); icl_pdu_free(request); cfiscsi_session_terminate(cs); return; } io = cdw->cdw_ctl_io; KASSERT((io->io_hdr.flags & CTL_FLAG_DATA_MASK) != CTL_FLAG_DATA_IN, ("CTL_FLAG_DATA_IN")); done = cfiscsi_handle_data_segment(request, cdw); if (done) { CFISCSI_SESSION_LOCK(cs); TAILQ_REMOVE(&cs->cs_waiting_for_data_out, cdw, cdw_next); CFISCSI_SESSION_UNLOCK(cs); done = (io->scsiio.ext_data_filled != cdw->cdw_r2t_end || io->scsiio.ext_data_filled == io->scsiio.kern_data_len); uma_zfree(cfiscsi_data_wait_zone, cdw); if (done) io->scsiio.be_move_done(io); else cfiscsi_datamove_out(io); } icl_pdu_free(request); } static void cfiscsi_pdu_handle_logout_request(struct icl_pdu *request) { struct iscsi_bhs_logout_request *bhslr; struct iscsi_bhs_logout_response *bhslr2; struct icl_pdu *response; struct cfiscsi_session *cs; cs = PDU_SESSION(request); bhslr = (struct iscsi_bhs_logout_request *)request->ip_bhs; switch (bhslr->bhslr_reason & 0x7f) { case BHSLR_REASON_CLOSE_SESSION: case BHSLR_REASON_CLOSE_CONNECTION: response = cfiscsi_pdu_new_response(request, M_NOWAIT); if (response == NULL) { CFISCSI_SESSION_DEBUG(cs, "failed to allocate memory"); icl_pdu_free(request); cfiscsi_session_terminate(cs); return; } bhslr2 = (struct iscsi_bhs_logout_response *)response->ip_bhs; bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE; bhslr2->bhslr_flags = 0x80; bhslr2->bhslr_response = BHSLR_RESPONSE_CLOSED_SUCCESSFULLY; bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag; icl_pdu_free(request); cfiscsi_pdu_queue(response); cfiscsi_session_terminate(cs); break; case BHSLR_REASON_REMOVE_FOR_RECOVERY: response = cfiscsi_pdu_new_response(request, M_NOWAIT); if (response == NULL) { CFISCSI_SESSION_WARN(cs, "failed to allocate memory; dropping connection"); icl_pdu_free(request); cfiscsi_session_terminate(cs); return; } bhslr2 = (struct iscsi_bhs_logout_response *)response->ip_bhs; bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE; bhslr2->bhslr_flags = 0x80; bhslr2->bhslr_response = BHSLR_RESPONSE_RECOVERY_NOT_SUPPORTED; bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag; icl_pdu_free(request); cfiscsi_pdu_queue(response); break; default: CFISCSI_SESSION_WARN(cs, "invalid reason 0%x; dropping connection", bhslr->bhslr_reason); icl_pdu_free(request); cfiscsi_session_terminate(cs); break; } } static void cfiscsi_callout(void *context) { struct icl_pdu *cp; struct iscsi_bhs_nop_in *bhsni; struct cfiscsi_session *cs; cs = context; if (cs->cs_terminating) return; callout_schedule(&cs->cs_callout, 1 * hz); atomic_add_int(&cs->cs_timeout, 1); #ifdef ICL_KERNEL_PROXY if (cs->cs_waiting_for_ctld || cs->cs_login_phase) { if (login_timeout > 0 && cs->cs_timeout > login_timeout) { CFISCSI_SESSION_WARN(cs, "login timed out after " "%d seconds; dropping connection", cs->cs_timeout); cfiscsi_session_terminate(cs); } return; } #endif if (ping_timeout <= 0) { /* * Pings are disabled. Don't send NOP-In in this case; * user might have disabled pings to work around problems * with certain initiators that can't properly handle * NOP-In, such as iPXE. Reset the timeout, to avoid * triggering reconnection, should the user decide to * reenable them. */ cs->cs_timeout = 0; return; } if (cs->cs_timeout >= ping_timeout) { CFISCSI_SESSION_WARN(cs, "no ping reply (NOP-Out) after %d seconds; " "dropping connection", ping_timeout); cfiscsi_session_terminate(cs); return; } /* * If the ping was reset less than one second ago - which means * that we've received some PDU during the last second - assume * the traffic flows correctly and don't bother sending a NOP-Out. * * (It's 2 - one for one second, and one for incrementing is_timeout * earlier in this routine.) */ if (cs->cs_timeout < 2) return; cp = icl_pdu_new(cs->cs_conn, M_NOWAIT); if (cp == NULL) { CFISCSI_SESSION_WARN(cs, "failed to allocate memory"); return; } bhsni = (struct iscsi_bhs_nop_in *)cp->ip_bhs; bhsni->bhsni_opcode = ISCSI_BHS_OPCODE_NOP_IN; bhsni->bhsni_flags = 0x80; bhsni->bhsni_initiator_task_tag = 0xffffffff; cfiscsi_pdu_queue(cp); } static void cfiscsi_session_terminate_tasks(struct cfiscsi_session *cs) { struct cfiscsi_data_wait *cdw; union ctl_io *io; int error, last; io = ctl_alloc_io(cs->cs_target->ct_port.ctl_pool_ref); if (io == NULL) { CFISCSI_SESSION_WARN(cs, "can't allocate ctl_io"); return; } ctl_zero_io(io); io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = cs; io->io_hdr.io_type = CTL_IO_TASK; io->io_hdr.nexus.initid.id = cs->cs_ctl_initid; io->io_hdr.nexus.targ_port = cs->cs_target->ct_port.targ_port; io->io_hdr.nexus.targ_target.id = 0; io->io_hdr.nexus.targ_lun = 0; io->taskio.tag_type = CTL_TAG_SIMPLE; /* XXX */ io->taskio.task_action = CTL_TASK_I_T_NEXUS_RESET; refcount_acquire(&cs->cs_outstanding_ctl_pdus); error = ctl_queue(io); if (error != CTL_RETVAL_COMPLETE) { CFISCSI_SESSION_WARN(cs, "ctl_queue() failed; error %d", error); refcount_release(&cs->cs_outstanding_ctl_pdus); ctl_free_io(io); } CFISCSI_SESSION_LOCK(cs); while ((cdw = TAILQ_FIRST(&cs->cs_waiting_for_data_out)) != NULL) { TAILQ_REMOVE(&cs->cs_waiting_for_data_out, cdw, cdw_next); CFISCSI_SESSION_UNLOCK(cs); /* * Set nonzero port status; this prevents backends from * assuming that the data transfer actually succeeded * and writing uninitialized data to disk. */ cdw->cdw_ctl_io->scsiio.io_hdr.port_status = 42; cdw->cdw_ctl_io->scsiio.be_move_done(cdw->cdw_ctl_io); uma_zfree(cfiscsi_data_wait_zone, cdw); CFISCSI_SESSION_LOCK(cs); } CFISCSI_SESSION_UNLOCK(cs); /* * Wait for CTL to terminate all the tasks. */ for (;;) { refcount_acquire(&cs->cs_outstanding_ctl_pdus); last = refcount_release(&cs->cs_outstanding_ctl_pdus); if (last != 0) break; CFISCSI_SESSION_WARN(cs, "waiting for CTL to terminate tasks, " "%d remaining", cs->cs_outstanding_ctl_pdus); tsleep(__DEVOLATILE(void *, &cs->cs_outstanding_ctl_pdus), 0, "cfiscsi_terminate", hz / 100); } } static void cfiscsi_maintenance_thread(void *arg) { struct cfiscsi_session *cs; cs = arg; for (;;) { CFISCSI_SESSION_LOCK(cs); if (cs->cs_terminating == false) cv_wait(&cs->cs_maintenance_cv, &cs->cs_lock); CFISCSI_SESSION_UNLOCK(cs); if (cs->cs_terminating) { /* * We used to wait up to 30 seconds to deliver queued * PDUs to the initiator. We also tried hard to deliver * SCSI Responses for the aborted PDUs. We don't do * that anymore. We might need to revisit that. */ callout_drain(&cs->cs_callout); icl_conn_close(cs->cs_conn); /* * At this point ICL receive thread is no longer * running; no new tasks can be queued. */ cfiscsi_session_terminate_tasks(cs); cfiscsi_session_delete(cs); kthread_exit(); return; } CFISCSI_SESSION_DEBUG(cs, "nothing to do"); } } static void cfiscsi_session_terminate(struct cfiscsi_session *cs) { if (cs->cs_terminating) return; cs->cs_terminating = true; cv_signal(&cs->cs_maintenance_cv); #ifdef ICL_KERNEL_PROXY cv_signal(&cs->cs_login_cv); #endif } static int cfiscsi_session_register_initiator(struct cfiscsi_session *cs) { struct cfiscsi_target *ct; char *name; int i; KASSERT(cs->cs_ctl_initid == -1, ("already registered")); ct = cs->cs_target; name = strdup(cs->cs_initiator_id, M_CTL); i = ctl_add_initiator(&ct->ct_port, -1, 0, name); if (i < 0) { CFISCSI_SESSION_WARN(cs, "ctl_add_initiator failed with error %d", i); cs->cs_ctl_initid = -1; return (1); } cs->cs_ctl_initid = i; #if 0 CFISCSI_SESSION_DEBUG(cs, "added initiator id %d", i); #endif return (0); } static void cfiscsi_session_unregister_initiator(struct cfiscsi_session *cs) { int error; if (cs->cs_ctl_initid == -1) return; error = ctl_remove_initiator(&cs->cs_target->ct_port, cs->cs_ctl_initid); if (error != 0) { CFISCSI_SESSION_WARN(cs, "ctl_remove_initiator failed with error %d", error); } cs->cs_ctl_initid = -1; } static struct cfiscsi_session * cfiscsi_session_new(struct cfiscsi_softc *softc) { struct cfiscsi_session *cs; int error; cs = malloc(sizeof(*cs), M_CFISCSI, M_NOWAIT | M_ZERO); if (cs == NULL) { CFISCSI_WARN("malloc failed"); return (NULL); } cs->cs_ctl_initid = -1; refcount_init(&cs->cs_outstanding_ctl_pdus, 0); TAILQ_INIT(&cs->cs_waiting_for_data_out); mtx_init(&cs->cs_lock, "cfiscsi_lock", NULL, MTX_DEF); cv_init(&cs->cs_maintenance_cv, "cfiscsi_mt"); #ifdef ICL_KERNEL_PROXY cv_init(&cs->cs_login_cv, "cfiscsi_login"); #endif cs->cs_conn = icl_conn_new("cfiscsi", &cs->cs_lock); cs->cs_conn->ic_receive = cfiscsi_receive_callback; cs->cs_conn->ic_error = cfiscsi_error_callback; cs->cs_conn->ic_prv0 = cs; error = kthread_add(cfiscsi_maintenance_thread, cs, NULL, NULL, 0, 0, "cfiscsimt"); if (error != 0) { CFISCSI_SESSION_WARN(cs, "kthread_add(9) failed with error %d", error); free(cs, M_CFISCSI); return (NULL); } mtx_lock(&softc->lock); cs->cs_id = softc->last_session_id + 1; softc->last_session_id++; mtx_unlock(&softc->lock); mtx_lock(&softc->lock); TAILQ_INSERT_TAIL(&softc->sessions, cs, cs_next); mtx_unlock(&softc->lock); /* * Start pinging the initiator. */ callout_init(&cs->cs_callout, 1); callout_reset(&cs->cs_callout, 1 * hz, cfiscsi_callout, cs); return (cs); } static void cfiscsi_session_delete(struct cfiscsi_session *cs) { struct cfiscsi_softc *softc; softc = &cfiscsi_softc; KASSERT(cs->cs_outstanding_ctl_pdus == 0, ("destroying session with outstanding CTL pdus")); KASSERT(TAILQ_EMPTY(&cs->cs_waiting_for_data_out), ("destroying session with non-empty queue")); cfiscsi_session_unregister_initiator(cs); if (cs->cs_target != NULL) cfiscsi_target_release(cs->cs_target); icl_conn_close(cs->cs_conn); icl_conn_free(cs->cs_conn); mtx_lock(&softc->lock); TAILQ_REMOVE(&softc->sessions, cs, cs_next); mtx_unlock(&softc->lock); free(cs, M_CFISCSI); } int cfiscsi_init(void) { struct cfiscsi_softc *softc; int retval; softc = &cfiscsi_softc; retval = 0; bzero(softc, sizeof(*softc)); mtx_init(&softc->lock, "cfiscsi", NULL, MTX_DEF); #ifdef ICL_KERNEL_PROXY cv_init(&softc->accept_cv, "cfiscsi_accept"); #endif TAILQ_INIT(&softc->sessions); TAILQ_INIT(&softc->targets); cfiscsi_data_wait_zone = uma_zcreate("cfiscsi_data_wait", sizeof(struct cfiscsi_data_wait), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); return (0); } #ifdef ICL_KERNEL_PROXY static void cfiscsi_accept(struct socket *so, struct sockaddr *sa, int portal_id) { struct cfiscsi_session *cs; cs = cfiscsi_session_new(&cfiscsi_softc); if (cs == NULL) { CFISCSI_WARN("failed to create session"); return; } icl_conn_handoff_sock(cs->cs_conn, so); cs->cs_initiator_sa = sa; cs->cs_portal_id = portal_id; cs->cs_waiting_for_ctld = true; cv_signal(&cfiscsi_softc.accept_cv); } #endif static void cfiscsi_online(void *arg) { struct cfiscsi_softc *softc; struct cfiscsi_target *ct; int online; ct = (struct cfiscsi_target *)arg; softc = ct->ct_softc; mtx_lock(&softc->lock); if (ct->ct_online) { mtx_unlock(&softc->lock); return; } ct->ct_online = 1; online = softc->online++; mtx_unlock(&softc->lock); if (online > 0) return; #ifdef ICL_KERNEL_PROXY if (softc->listener != NULL) icl_listen_free(softc->listener); softc->listener = icl_listen_new(cfiscsi_accept); #endif } static void cfiscsi_offline(void *arg) { struct cfiscsi_softc *softc; struct cfiscsi_target *ct; struct cfiscsi_session *cs; int online; ct = (struct cfiscsi_target *)arg; softc = ct->ct_softc; mtx_lock(&softc->lock); if (!ct->ct_online) { mtx_unlock(&softc->lock); return; } ct->ct_online = 0; online = --softc->online; TAILQ_FOREACH(cs, &softc->sessions, cs_next) { if (cs->cs_target == ct) cfiscsi_session_terminate(cs); } mtx_unlock(&softc->lock); if (online > 0) return; #ifdef ICL_KERNEL_PROXY icl_listen_free(softc->listener); softc->listener = NULL; #endif } static int cfiscsi_info(void *arg, struct sbuf *sb) { struct cfiscsi_target *ct = (struct cfiscsi_target *)arg; int retval; retval = sbuf_printf(sb, "\t%d\n", ct->ct_state); return (retval); } static void cfiscsi_ioctl_handoff(struct ctl_iscsi *ci) { struct cfiscsi_softc *softc; struct cfiscsi_session *cs, *cs2; struct cfiscsi_target *ct; struct ctl_iscsi_handoff_params *cihp; int error; cihp = (struct ctl_iscsi_handoff_params *)&(ci->data); softc = &cfiscsi_softc; CFISCSI_DEBUG("new connection from %s (%s) to %s", cihp->initiator_name, cihp->initiator_addr, cihp->target_name); ct = cfiscsi_target_find(softc, cihp->target_name); if (ct == NULL) { ci->status = CTL_ISCSI_ERROR; snprintf(ci->error_str, sizeof(ci->error_str), "%s: target not found", __func__); return; } if (ct->ct_online == 0) { ci->status = CTL_ISCSI_ERROR; snprintf(ci->error_str, sizeof(ci->error_str), "%s: port offline", __func__); cfiscsi_target_release(ct); return; } #ifdef ICL_KERNEL_PROXY if (cihp->socket > 0 && cihp->connection_id > 0) { snprintf(ci->error_str, sizeof(ci->error_str), "both socket and connection_id set"); ci->status = CTL_ISCSI_ERROR; cfiscsi_target_release(ct); return; } if (cihp->socket == 0) { mtx_lock(&cfiscsi_softc.lock); TAILQ_FOREACH(cs, &cfiscsi_softc.sessions, cs_next) { if (cs->cs_id == cihp->socket) break; } if (cs == NULL) { mtx_unlock(&cfiscsi_softc.lock); snprintf(ci->error_str, sizeof(ci->error_str), "connection not found"); ci->status = CTL_ISCSI_ERROR; cfiscsi_target_release(ct); return; } mtx_unlock(&cfiscsi_softc.lock); } else { #endif cs = cfiscsi_session_new(softc); if (cs == NULL) { ci->status = CTL_ISCSI_ERROR; snprintf(ci->error_str, sizeof(ci->error_str), "%s: cfiscsi_session_new failed", __func__); cfiscsi_target_release(ct); return; } #ifdef ICL_KERNEL_PROXY } #endif cs->cs_target = ct; /* * First PDU of Full Feature phase has the same CmdSN as the last * PDU from the Login Phase received from the initiator. Thus, * the -1 below. */ cs->cs_portal_group_tag = cihp->portal_group_tag; cs->cs_cmdsn = cihp->cmdsn; cs->cs_statsn = cihp->statsn; cs->cs_max_data_segment_length = cihp->max_recv_data_segment_length; cs->cs_max_burst_length = cihp->max_burst_length; cs->cs_immediate_data = !!cihp->immediate_data; if (cihp->header_digest == CTL_ISCSI_DIGEST_CRC32C) cs->cs_conn->ic_header_crc32c = true; if (cihp->data_digest == CTL_ISCSI_DIGEST_CRC32C) cs->cs_conn->ic_data_crc32c = true; strlcpy(cs->cs_initiator_name, cihp->initiator_name, sizeof(cs->cs_initiator_name)); strlcpy(cs->cs_initiator_addr, cihp->initiator_addr, sizeof(cs->cs_initiator_addr)); strlcpy(cs->cs_initiator_alias, cihp->initiator_alias, sizeof(cs->cs_initiator_alias)); memcpy(cs->cs_initiator_isid, cihp->initiator_isid, sizeof(cs->cs_initiator_isid)); snprintf(cs->cs_initiator_id, sizeof(cs->cs_initiator_id), "%s,i,0x%02x%02x%02x%02x%02x%02x", cs->cs_initiator_name, cihp->initiator_isid[0], cihp->initiator_isid[1], cihp->initiator_isid[2], cihp->initiator_isid[3], cihp->initiator_isid[4], cihp->initiator_isid[5]); refcount_acquire(&cs->cs_outstanding_ctl_pdus); restart: if (!cs->cs_terminating) { mtx_lock(&softc->lock); TAILQ_FOREACH(cs2, &softc->sessions, cs_next) { if (cs2 != cs && cs2->cs_tasks_aborted == false && cs->cs_target == cs2->cs_target && cs->cs_portal_group_tag == cs2->cs_portal_group_tag && strcmp(cs->cs_initiator_id, cs2->cs_initiator_id) == 0) { cfiscsi_session_terminate(cs2); mtx_unlock(&softc->lock); pause("cfiscsi_reinstate", 1); goto restart; } } mtx_unlock(&softc->lock); } /* * Register initiator with CTL. */ cfiscsi_session_register_initiator(cs); #ifdef ICL_KERNEL_PROXY if (cihp->socket > 0) { #endif error = icl_conn_handoff(cs->cs_conn, cihp->socket); if (error != 0) { cfiscsi_session_terminate(cs); refcount_release(&cs->cs_outstanding_ctl_pdus); ci->status = CTL_ISCSI_ERROR; snprintf(ci->error_str, sizeof(ci->error_str), "%s: icl_conn_handoff failed with error %d", __func__, error); return; } #ifdef ICL_KERNEL_PROXY } #endif #ifdef ICL_KERNEL_PROXY cs->cs_login_phase = false; /* * First PDU of the Full Feature phase has likely already arrived. * We have to pick it up and execute properly. */ if (cs->cs_login_pdu != NULL) { CFISCSI_SESSION_DEBUG(cs, "picking up first PDU"); cfiscsi_pdu_handle(cs->cs_login_pdu); cs->cs_login_pdu = NULL; } #endif refcount_release(&cs->cs_outstanding_ctl_pdus); ci->status = CTL_ISCSI_OK; } static void cfiscsi_ioctl_list(struct ctl_iscsi *ci) { struct ctl_iscsi_list_params *cilp; struct cfiscsi_session *cs; struct cfiscsi_softc *softc; struct sbuf *sb; int error; cilp = (struct ctl_iscsi_list_params *)&(ci->data); softc = &cfiscsi_softc; sb = sbuf_new(NULL, NULL, cilp->alloc_len, SBUF_FIXEDLEN); if (sb == NULL) { ci->status = CTL_ISCSI_ERROR; snprintf(ci->error_str, sizeof(ci->error_str), "Unable to allocate %d bytes for iSCSI session list", cilp->alloc_len); return; } sbuf_printf(sb, "\n"); mtx_lock(&softc->lock); TAILQ_FOREACH(cs, &softc->sessions, cs_next) { #ifdef ICL_KERNEL_PROXY if (cs->cs_target == NULL) continue; #endif error = sbuf_printf(sb, "" "%s" "%s" "%s" "%s" "%s" "%s" "%s" "%zd" "%d" "%d" "\n", cs->cs_id, cs->cs_initiator_name, cs->cs_initiator_addr, cs->cs_initiator_alias, cs->cs_target->ct_name, cs->cs_target->ct_alias, cs->cs_conn->ic_header_crc32c ? "CRC32C" : "None", cs->cs_conn->ic_data_crc32c ? "CRC32C" : "None", cs->cs_max_data_segment_length, cs->cs_immediate_data, cs->cs_conn->ic_iser); if (error != 0) break; } mtx_unlock(&softc->lock); error = sbuf_printf(sb, "\n"); if (error != 0) { sbuf_delete(sb); ci->status = CTL_ISCSI_LIST_NEED_MORE_SPACE; snprintf(ci->error_str, sizeof(ci->error_str), "Out of space, %d bytes is too small", cilp->alloc_len); return; } sbuf_finish(sb); error = copyout(sbuf_data(sb), cilp->conn_xml, sbuf_len(sb) + 1); cilp->fill_len = sbuf_len(sb) + 1; ci->status = CTL_ISCSI_OK; sbuf_delete(sb); } static void cfiscsi_ioctl_terminate(struct ctl_iscsi *ci) { struct icl_pdu *response; struct iscsi_bhs_asynchronous_message *bhsam; struct ctl_iscsi_terminate_params *citp; struct cfiscsi_session *cs; struct cfiscsi_softc *softc; int found = 0; citp = (struct ctl_iscsi_terminate_params *)&(ci->data); softc = &cfiscsi_softc; mtx_lock(&softc->lock); TAILQ_FOREACH(cs, &softc->sessions, cs_next) { if (citp->all == 0 && cs->cs_id != citp->connection_id && strcmp(cs->cs_initiator_name, citp->initiator_name) != 0 && strcmp(cs->cs_initiator_addr, citp->initiator_addr) != 0) continue; response = icl_pdu_new(cs->cs_conn, M_NOWAIT); if (response == NULL) { /* * Oh well. Just terminate the connection. */ } else { bhsam = (struct iscsi_bhs_asynchronous_message *) response->ip_bhs; bhsam->bhsam_opcode = ISCSI_BHS_OPCODE_ASYNC_MESSAGE; bhsam->bhsam_flags = 0x80; bhsam->bhsam_0xffffffff = 0xffffffff; bhsam->bhsam_async_event = BHSAM_EVENT_TARGET_TERMINATES_SESSION; cfiscsi_pdu_queue(response); } cfiscsi_session_terminate(cs); found++; } mtx_unlock(&softc->lock); if (found == 0) { ci->status = CTL_ISCSI_SESSION_NOT_FOUND; snprintf(ci->error_str, sizeof(ci->error_str), "No matching connections found"); return; } ci->status = CTL_ISCSI_OK; } static void cfiscsi_ioctl_logout(struct ctl_iscsi *ci) { struct icl_pdu *response; struct iscsi_bhs_asynchronous_message *bhsam; struct ctl_iscsi_logout_params *cilp; struct cfiscsi_session *cs; struct cfiscsi_softc *softc; int found = 0; cilp = (struct ctl_iscsi_logout_params *)&(ci->data); softc = &cfiscsi_softc; mtx_lock(&softc->lock); TAILQ_FOREACH(cs, &softc->sessions, cs_next) { if (cilp->all == 0 && cs->cs_id != cilp->connection_id && strcmp(cs->cs_initiator_name, cilp->initiator_name) != 0 && strcmp(cs->cs_initiator_addr, cilp->initiator_addr) != 0) continue; response = icl_pdu_new(cs->cs_conn, M_NOWAIT); if (response == NULL) { ci->status = CTL_ISCSI_ERROR; snprintf(ci->error_str, sizeof(ci->error_str), "Unable to allocate memory"); mtx_unlock(&softc->lock); return; } bhsam = (struct iscsi_bhs_asynchronous_message *)response->ip_bhs; bhsam->bhsam_opcode = ISCSI_BHS_OPCODE_ASYNC_MESSAGE; bhsam->bhsam_flags = 0x80; bhsam->bhsam_async_event = BHSAM_EVENT_TARGET_REQUESTS_LOGOUT; bhsam->bhsam_parameter3 = htons(10); cfiscsi_pdu_queue(response); found++; } mtx_unlock(&softc->lock); if (found == 0) { ci->status = CTL_ISCSI_SESSION_NOT_FOUND; snprintf(ci->error_str, sizeof(ci->error_str), "No matching connections found"); return; } ci->status = CTL_ISCSI_OK; } #ifdef ICL_KERNEL_PROXY static void cfiscsi_ioctl_listen(struct ctl_iscsi *ci) { struct ctl_iscsi_listen_params *cilp; struct sockaddr *sa; int error; cilp = (struct ctl_iscsi_listen_params *)&(ci->data); if (cfiscsi_softc.listener == NULL) { CFISCSI_DEBUG("no listener"); snprintf(ci->error_str, sizeof(ci->error_str), "no listener"); ci->status = CTL_ISCSI_ERROR; return; } error = getsockaddr(&sa, (void *)cilp->addr, cilp->addrlen); if (error != 0) { CFISCSI_DEBUG("getsockaddr, error %d", error); snprintf(ci->error_str, sizeof(ci->error_str), "getsockaddr failed"); ci->status = CTL_ISCSI_ERROR; return; } error = icl_listen_add(cfiscsi_softc.listener, cilp->iser, cilp->domain, cilp->socktype, cilp->protocol, sa, cilp->portal_id); if (error != 0) { free(sa, M_SONAME); CFISCSI_DEBUG("icl_listen_add, error %d", error); snprintf(ci->error_str, sizeof(ci->error_str), "icl_listen_add failed, error %d", error); ci->status = CTL_ISCSI_ERROR; return; } ci->status = CTL_ISCSI_OK; } static void cfiscsi_ioctl_accept(struct ctl_iscsi *ci) { struct ctl_iscsi_accept_params *ciap; struct cfiscsi_session *cs; int error; ciap = (struct ctl_iscsi_accept_params *)&(ci->data); mtx_lock(&cfiscsi_softc.lock); for (;;) { TAILQ_FOREACH(cs, &cfiscsi_softc.sessions, cs_next) { if (cs->cs_waiting_for_ctld) break; } if (cs != NULL) break; error = cv_wait_sig(&cfiscsi_softc.accept_cv, &cfiscsi_softc.lock); if (error != 0) { mtx_unlock(&cfiscsi_softc.lock); snprintf(ci->error_str, sizeof(ci->error_str), "interrupted"); ci->status = CTL_ISCSI_ERROR; return; } } mtx_unlock(&cfiscsi_softc.lock); cs->cs_waiting_for_ctld = false; cs->cs_login_phase = true; ciap->connection_id = cs->cs_id; ciap->portal_id = cs->cs_portal_id; ciap->initiator_addrlen = cs->cs_initiator_sa->sa_len; error = copyout(cs->cs_initiator_sa, ciap->initiator_addr, cs->cs_initiator_sa->sa_len); if (error != 0) { snprintf(ci->error_str, sizeof(ci->error_str), "copyout failed with error %d", error); ci->status = CTL_ISCSI_ERROR; return; } ci->status = CTL_ISCSI_OK; } static void cfiscsi_ioctl_send(struct ctl_iscsi *ci) { struct ctl_iscsi_send_params *cisp; struct cfiscsi_session *cs; struct icl_pdu *ip; size_t datalen; void *data; int error; cisp = (struct ctl_iscsi_send_params *)&(ci->data); mtx_lock(&cfiscsi_softc.lock); TAILQ_FOREACH(cs, &cfiscsi_softc.sessions, cs_next) { if (cs->cs_id == cisp->connection_id) break; } if (cs == NULL) { mtx_unlock(&cfiscsi_softc.lock); snprintf(ci->error_str, sizeof(ci->error_str), "connection not found"); ci->status = CTL_ISCSI_ERROR; return; } mtx_unlock(&cfiscsi_softc.lock); #if 0 if (cs->cs_login_phase == false) return (EBUSY); #endif if (cs->cs_terminating) { snprintf(ci->error_str, sizeof(ci->error_str), "connection is terminating"); ci->status = CTL_ISCSI_ERROR; return; } datalen = cisp->data_segment_len; /* * XXX */ //if (datalen > CFISCSI_MAX_DATA_SEGMENT_LENGTH) { if (datalen > 65535) { snprintf(ci->error_str, sizeof(ci->error_str), "data segment too big"); ci->status = CTL_ISCSI_ERROR; return; } if (datalen > 0) { data = malloc(datalen, M_CFISCSI, M_WAITOK); error = copyin(cisp->data_segment, data, datalen); if (error != 0) { free(data, M_CFISCSI); snprintf(ci->error_str, sizeof(ci->error_str), "copyin error %d", error); ci->status = CTL_ISCSI_ERROR; return; } } ip = icl_pdu_new(cs->cs_conn, M_WAITOK); memcpy(ip->ip_bhs, cisp->bhs, sizeof(*ip->ip_bhs)); if (datalen > 0) { icl_pdu_append_data(ip, data, datalen, M_WAITOK); free(data, M_CFISCSI); } CFISCSI_SESSION_LOCK(cs); icl_pdu_queue(ip); CFISCSI_SESSION_UNLOCK(cs); ci->status = CTL_ISCSI_OK; } static void cfiscsi_ioctl_receive(struct ctl_iscsi *ci) { struct ctl_iscsi_receive_params *cirp; struct cfiscsi_session *cs; struct icl_pdu *ip; void *data; int error; cirp = (struct ctl_iscsi_receive_params *)&(ci->data); mtx_lock(&cfiscsi_softc.lock); TAILQ_FOREACH(cs, &cfiscsi_softc.sessions, cs_next) { if (cs->cs_id == cirp->connection_id) break; } if (cs == NULL) { mtx_unlock(&cfiscsi_softc.lock); snprintf(ci->error_str, sizeof(ci->error_str), "connection not found"); ci->status = CTL_ISCSI_ERROR; return; } mtx_unlock(&cfiscsi_softc.lock); #if 0 if (is->is_login_phase == false) return (EBUSY); #endif CFISCSI_SESSION_LOCK(cs); while (cs->cs_login_pdu == NULL && cs->cs_terminating == false) { error = cv_wait_sig(&cs->cs_login_cv, &cs->cs_lock); if (error != 0) { CFISCSI_SESSION_UNLOCK(cs); snprintf(ci->error_str, sizeof(ci->error_str), "interrupted by signal"); ci->status = CTL_ISCSI_ERROR; return; } } if (cs->cs_terminating) { CFISCSI_SESSION_UNLOCK(cs); snprintf(ci->error_str, sizeof(ci->error_str), "connection terminating"); ci->status = CTL_ISCSI_ERROR; return; } ip = cs->cs_login_pdu; cs->cs_login_pdu = NULL; CFISCSI_SESSION_UNLOCK(cs); if (ip->ip_data_len > cirp->data_segment_len) { icl_pdu_free(ip); snprintf(ci->error_str, sizeof(ci->error_str), "data segment too big"); ci->status = CTL_ISCSI_ERROR; return; } copyout(ip->ip_bhs, cirp->bhs, sizeof(*ip->ip_bhs)); if (ip->ip_data_len > 0) { data = malloc(ip->ip_data_len, M_CFISCSI, M_WAITOK); icl_pdu_get_data(ip, 0, data, ip->ip_data_len); copyout(data, cirp->data_segment, ip->ip_data_len); free(data, M_CFISCSI); } icl_pdu_free(ip); ci->status = CTL_ISCSI_OK; } #endif /* !ICL_KERNEL_PROXY */ static void cfiscsi_ioctl_port_create(struct ctl_req *req) { struct cfiscsi_target *ct; struct ctl_port *port; const char *target, *alias, *tag; struct scsi_vpd_id_descriptor *desc; ctl_options_t opts; int retval, len, idlen; ctl_init_opts(&opts, req->num_args, req->kern_args); target = ctl_get_opt(&opts, "cfiscsi_target"); alias = ctl_get_opt(&opts, "cfiscsi_target_alias"); tag = ctl_get_opt(&opts, "cfiscsi_portal_group_tag"); if (target == NULL || tag == NULL) { req->status = CTL_LUN_ERROR; snprintf(req->error_str, sizeof(req->error_str), "Missing required argument"); ctl_free_opts(&opts); return; } ct = cfiscsi_target_find_or_create(&cfiscsi_softc, target, alias); if (ct == NULL) { req->status = CTL_LUN_ERROR; snprintf(req->error_str, sizeof(req->error_str), "failed to create target \"%s\"", target); ctl_free_opts(&opts); return; } if (ct->ct_state == CFISCSI_TARGET_STATE_ACTIVE) { req->status = CTL_LUN_ERROR; snprintf(req->error_str, sizeof(req->error_str), - "target \"%s\" already exist", target); + "target \"%s\" already exists", target); cfiscsi_target_release(ct); ctl_free_opts(&opts); return; } port = &ct->ct_port; if (ct->ct_state == CFISCSI_TARGET_STATE_DYING) goto done; port->frontend = &cfiscsi_frontend; port->port_type = CTL_PORT_ISCSI; /* XXX KDM what should the real number be here? */ port->num_requested_ctl_io = 4096; port->port_name = "iscsi"; port->physical_port = strtoul(tag, NULL, 0); port->virtual_port = ct->ct_target_id; port->port_online = cfiscsi_online; port->port_offline = cfiscsi_offline; port->port_info = cfiscsi_info; port->onoff_arg = ct; port->lun_enable = cfiscsi_lun_enable; port->lun_disable = cfiscsi_lun_disable; port->lun_map = cfiscsi_lun_map; port->targ_lun_arg = ct; port->fe_datamove = cfiscsi_datamove; port->fe_done = cfiscsi_done; /* XXX KDM what should we report here? */ /* XXX These should probably be fetched from CTL. */ port->max_targets = 1; port->max_target_id = 15; port->options = opts; STAILQ_INIT(&opts); /* Generate Port ID. */ idlen = strlen(target) + strlen(",t,0x0001") + 1; idlen = roundup2(idlen, 4); len = sizeof(struct scsi_vpd_device_id) + idlen; port->port_devid = malloc(sizeof(struct ctl_devid) + len, M_CTL, M_WAITOK | M_ZERO); port->port_devid->len = len; desc = (struct scsi_vpd_id_descriptor *)port->port_devid->data; desc->proto_codeset = (SCSI_PROTO_ISCSI << 4) | SVPD_ID_CODESET_UTF8; desc->id_type = SVPD_ID_PIV | SVPD_ID_ASSOC_PORT | SVPD_ID_TYPE_SCSI_NAME; desc->length = idlen; snprintf(desc->identifier, idlen, "%s,t,0x%4.4x", target, port->physical_port); /* Generate Target ID. */ idlen = strlen(target) + 1; idlen = roundup2(idlen, 4); len = sizeof(struct scsi_vpd_device_id) + idlen; port->target_devid = malloc(sizeof(struct ctl_devid) + len, M_CTL, M_WAITOK | M_ZERO); port->target_devid->len = len; desc = (struct scsi_vpd_id_descriptor *)port->target_devid->data; desc->proto_codeset = (SCSI_PROTO_ISCSI << 4) | SVPD_ID_CODESET_UTF8; desc->id_type = SVPD_ID_PIV | SVPD_ID_ASSOC_TARGET | SVPD_ID_TYPE_SCSI_NAME; desc->length = idlen; strlcpy(desc->identifier, target, idlen); retval = ctl_port_register(port, /*master_SC*/ 1); if (retval != 0) { ctl_free_opts(&port->options); cfiscsi_target_release(ct); free(port->port_devid, M_CFISCSI); free(port->target_devid, M_CFISCSI); req->status = CTL_LUN_ERROR; snprintf(req->error_str, sizeof(req->error_str), "ctl_frontend_register() failed with error %d", retval); return; } done: ct->ct_state = CFISCSI_TARGET_STATE_ACTIVE; req->status = CTL_LUN_OK; memcpy(req->kern_args[0].kvalue, &port->targ_port, sizeof(port->targ_port)); //XXX } static void cfiscsi_ioctl_port_remove(struct ctl_req *req) { struct cfiscsi_target *ct; const char *target; ctl_options_t opts; ctl_init_opts(&opts, req->num_args, req->kern_args); target = ctl_get_opt(&opts, "cfiscsi_target"); if (target == NULL) { ctl_free_opts(&opts); req->status = CTL_LUN_ERROR; snprintf(req->error_str, sizeof(req->error_str), "Missing required argument"); return; } ct = cfiscsi_target_find(&cfiscsi_softc, target); if (ct == NULL) { ctl_free_opts(&opts); req->status = CTL_LUN_ERROR; snprintf(req->error_str, sizeof(req->error_str), "can't find target \"%s\"", target); return; } if (ct->ct_state != CFISCSI_TARGET_STATE_ACTIVE) { ctl_free_opts(&opts); req->status = CTL_LUN_ERROR; snprintf(req->error_str, sizeof(req->error_str), "target \"%s\" is already dying", target); return; } ctl_free_opts(&opts); ct->ct_state = CFISCSI_TARGET_STATE_DYING; ctl_port_offline(&ct->ct_port); cfiscsi_target_release(ct); cfiscsi_target_release(ct); } static int cfiscsi_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td) { struct ctl_iscsi *ci; struct ctl_req *req; if (cmd == CTL_PORT_REQ) { req = (struct ctl_req *)addr; switch (req->reqtype) { case CTL_REQ_CREATE: cfiscsi_ioctl_port_create(req); break; case CTL_REQ_REMOVE: cfiscsi_ioctl_port_remove(req); break; default: req->status = CTL_LUN_ERROR; snprintf(req->error_str, sizeof(req->error_str), "Unsupported request type %d", req->reqtype); } return (0); } if (cmd != CTL_ISCSI) return (ENOTTY); ci = (struct ctl_iscsi *)addr; switch (ci->type) { case CTL_ISCSI_HANDOFF: cfiscsi_ioctl_handoff(ci); break; case CTL_ISCSI_LIST: cfiscsi_ioctl_list(ci); break; case CTL_ISCSI_TERMINATE: cfiscsi_ioctl_terminate(ci); break; case CTL_ISCSI_LOGOUT: cfiscsi_ioctl_logout(ci); break; #ifdef ICL_KERNEL_PROXY case CTL_ISCSI_LISTEN: cfiscsi_ioctl_listen(ci); break; case CTL_ISCSI_ACCEPT: cfiscsi_ioctl_accept(ci); break; case CTL_ISCSI_SEND: cfiscsi_ioctl_send(ci); break; case CTL_ISCSI_RECEIVE: cfiscsi_ioctl_receive(ci); break; #else case CTL_ISCSI_LISTEN: case CTL_ISCSI_ACCEPT: case CTL_ISCSI_SEND: case CTL_ISCSI_RECEIVE: ci->status = CTL_ISCSI_ERROR; snprintf(ci->error_str, sizeof(ci->error_str), "%s: CTL compiled without ICL_KERNEL_PROXY", __func__); break; #endif /* !ICL_KERNEL_PROXY */ default: ci->status = CTL_ISCSI_ERROR; snprintf(ci->error_str, sizeof(ci->error_str), "%s: invalid iSCSI request type %d", __func__, ci->type); break; } return (0); } static void cfiscsi_target_hold(struct cfiscsi_target *ct) { refcount_acquire(&ct->ct_refcount); } static void cfiscsi_target_release(struct cfiscsi_target *ct) { struct cfiscsi_softc *softc; softc = ct->ct_softc; mtx_lock(&softc->lock); if (refcount_release(&ct->ct_refcount)) { TAILQ_REMOVE(&softc->targets, ct, ct_next); mtx_unlock(&softc->lock); if (ct->ct_state != CFISCSI_TARGET_STATE_INVALID) { ct->ct_state = CFISCSI_TARGET_STATE_INVALID; if (ctl_port_deregister(&ct->ct_port) != 0) printf("%s: ctl_port_deregister() failed\n", __func__); } free(ct, M_CFISCSI); return; } mtx_unlock(&softc->lock); } static struct cfiscsi_target * cfiscsi_target_find(struct cfiscsi_softc *softc, const char *name) { struct cfiscsi_target *ct; mtx_lock(&softc->lock); TAILQ_FOREACH(ct, &softc->targets, ct_next) { if (strcmp(name, ct->ct_name) != 0 || ct->ct_state != CFISCSI_TARGET_STATE_ACTIVE) continue; cfiscsi_target_hold(ct); mtx_unlock(&softc->lock); return (ct); } mtx_unlock(&softc->lock); return (NULL); } static struct cfiscsi_target * cfiscsi_target_find_or_create(struct cfiscsi_softc *softc, const char *name, const char *alias) { struct cfiscsi_target *ct, *newct; int i; if (name[0] == '\0' || strlen(name) >= CTL_ISCSI_NAME_LEN) return (NULL); newct = malloc(sizeof(*newct), M_CFISCSI, M_WAITOK | M_ZERO); mtx_lock(&softc->lock); TAILQ_FOREACH(ct, &softc->targets, ct_next) { if (strcmp(name, ct->ct_name) != 0 || ct->ct_state == CFISCSI_TARGET_STATE_INVALID) continue; cfiscsi_target_hold(ct); mtx_unlock(&softc->lock); free(newct, M_CFISCSI); return (ct); } for (i = 0; i < CTL_MAX_LUNS; i++) newct->ct_luns[i] = UINT32_MAX; strlcpy(newct->ct_name, name, sizeof(newct->ct_name)); if (alias != NULL) strlcpy(newct->ct_alias, alias, sizeof(newct->ct_alias)); refcount_init(&newct->ct_refcount, 1); newct->ct_softc = softc; if (TAILQ_EMPTY(&softc->targets)) softc->last_target_id = 0; newct->ct_target_id = ++softc->last_target_id; TAILQ_INSERT_TAIL(&softc->targets, newct, ct_next); mtx_unlock(&softc->lock); return (newct); } /* * Takes LUN from the target space and returns LUN from the CTL space. */ static uint32_t cfiscsi_lun_map(void *arg, uint32_t lun) { struct cfiscsi_target *ct = arg; if (lun >= CTL_MAX_LUNS) { CFISCSI_DEBUG("requested lun number %d is higher " "than maximum %d", lun, CTL_MAX_LUNS - 1); return (UINT32_MAX); } return (ct->ct_luns[lun]); } static int cfiscsi_target_set_lun(struct cfiscsi_target *ct, unsigned long lun_id, unsigned long ctl_lun_id) { if (lun_id >= CTL_MAX_LUNS) { CFISCSI_WARN("requested lun number %ld is higher " "than maximum %d", lun_id, CTL_MAX_LUNS - 1); return (-1); } if (ct->ct_luns[lun_id] < CTL_MAX_LUNS) { /* * CTL calls cfiscsi_lun_enable() twice for each LUN - once * when the LUN is created, and a second time just before * the port is brought online; don't emit warnings * for that case. */ if (ct->ct_luns[lun_id] == ctl_lun_id) return (0); CFISCSI_WARN("lun %ld already allocated", lun_id); return (-1); } #if 0 CFISCSI_DEBUG("adding mapping for lun %ld, target %s " "to ctl lun %ld", lun_id, ct->ct_name, ctl_lun_id); #endif ct->ct_luns[lun_id] = ctl_lun_id; return (0); } static int cfiscsi_lun_enable(void *arg, struct ctl_id target_id, int lun_id) { struct cfiscsi_softc *softc; struct cfiscsi_target *ct; const char *target = NULL; const char *lun = NULL; unsigned long tmp; ct = (struct cfiscsi_target *)arg; softc = ct->ct_softc; target = ctl_get_opt(&control_softc->ctl_luns[lun_id]->be_lun->options, "cfiscsi_target"); lun = ctl_get_opt(&control_softc->ctl_luns[lun_id]->be_lun->options, "cfiscsi_lun"); if (target == NULL && lun == NULL) return (0); if (target == NULL || lun == NULL) { CFISCSI_WARN("lun added with cfiscsi_target, but without " "cfiscsi_lun, or the other way around; ignoring"); return (0); } if (strcmp(target, ct->ct_name) != 0) return (0); tmp = strtoul(lun, NULL, 10); cfiscsi_target_set_lun(ct, tmp, lun_id); return (0); } static int cfiscsi_lun_disable(void *arg, struct ctl_id target_id, int lun_id) { struct cfiscsi_softc *softc; struct cfiscsi_target *ct; int i; ct = (struct cfiscsi_target *)arg; softc = ct->ct_softc; mtx_lock(&softc->lock); for (i = 0; i < CTL_MAX_LUNS; i++) { if (ct->ct_luns[i] != lun_id) continue; ct->ct_luns[i] = UINT32_MAX; break; } mtx_unlock(&softc->lock); return (0); } static void cfiscsi_datamove_in(union ctl_io *io) { struct cfiscsi_session *cs; struct icl_pdu *request, *response; const struct iscsi_bhs_scsi_command *bhssc; struct iscsi_bhs_data_in *bhsdi; struct ctl_sg_entry ctl_sg_entry, *ctl_sglist; size_t len, expected_len, sg_len, buffer_offset; const char *sg_addr; int ctl_sg_count, error, i; request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; cs = PDU_SESSION(request); bhssc = (const struct iscsi_bhs_scsi_command *)request->ip_bhs; KASSERT((bhssc->bhssc_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == ISCSI_BHS_OPCODE_SCSI_COMMAND, ("bhssc->bhssc_opcode != ISCSI_BHS_OPCODE_SCSI_COMMAND")); if (io->scsiio.kern_sg_entries > 0) { ctl_sglist = (struct ctl_sg_entry *)io->scsiio.kern_data_ptr; ctl_sg_count = io->scsiio.kern_sg_entries; } else { ctl_sglist = &ctl_sg_entry; ctl_sglist->addr = io->scsiio.kern_data_ptr; ctl_sglist->len = io->scsiio.kern_data_len; ctl_sg_count = 1; } /* * This is the total amount of data to be transferred within the current * SCSI command. We need to record it so that we can properly report * underflow/underflow. */ PDU_TOTAL_TRANSFER_LEN(request) = io->scsiio.kern_total_len; /* * This is the offset within the current SCSI command; for the first * call to cfiscsi_datamove() it will be 0, and for subsequent ones * it will be the sum of lengths of previous ones. */ buffer_offset = io->scsiio.kern_rel_offset; /* * This is the transfer length expected by the initiator. In theory, * it could be different from the correct amount of data from the SCSI * point of view, even if that doesn't make any sense. */ expected_len = ntohl(bhssc->bhssc_expected_data_transfer_length); #if 0 if (expected_len != io->scsiio.kern_total_len) { CFISCSI_SESSION_DEBUG(cs, "expected transfer length %zd, " "actual length %zd", expected_len, (size_t)io->scsiio.kern_total_len); } #endif if (buffer_offset >= expected_len) { #if 0 CFISCSI_SESSION_DEBUG(cs, "buffer_offset = %zd, " "already sent the expected len", buffer_offset); #endif io->scsiio.be_move_done(io); return; } i = 0; sg_addr = NULL; sg_len = 0; response = NULL; bhsdi = NULL; for (;;) { if (response == NULL) { response = cfiscsi_pdu_new_response(request, M_NOWAIT); if (response == NULL) { CFISCSI_SESSION_WARN(cs, "failed to " "allocate memory; dropping connection"); ctl_set_busy(&io->scsiio); io->scsiio.be_move_done(io); cfiscsi_session_terminate(cs); return; } bhsdi = (struct iscsi_bhs_data_in *)response->ip_bhs; bhsdi->bhsdi_opcode = ISCSI_BHS_OPCODE_SCSI_DATA_IN; bhsdi->bhsdi_initiator_task_tag = bhssc->bhssc_initiator_task_tag; bhsdi->bhsdi_datasn = htonl(PDU_EXPDATASN(request)); PDU_EXPDATASN(request)++; bhsdi->bhsdi_buffer_offset = htonl(buffer_offset); } KASSERT(i < ctl_sg_count, ("i >= ctl_sg_count")); if (sg_len == 0) { sg_addr = ctl_sglist[i].addr; sg_len = ctl_sglist[i].len; KASSERT(sg_len > 0, ("sg_len <= 0")); } len = sg_len; /* * Truncate to maximum data segment length. */ KASSERT(response->ip_data_len < cs->cs_max_data_segment_length, ("ip_data_len %zd >= max_data_segment_length %zd", response->ip_data_len, cs->cs_max_data_segment_length)); if (response->ip_data_len + len > cs->cs_max_data_segment_length) { len = cs->cs_max_data_segment_length - response->ip_data_len; KASSERT(len <= sg_len, ("len %zd > sg_len %zd", len, sg_len)); } /* * Truncate to expected data transfer length. */ KASSERT(buffer_offset + response->ip_data_len < expected_len, ("buffer_offset %zd + ip_data_len %zd >= expected_len %zd", buffer_offset, response->ip_data_len, expected_len)); if (buffer_offset + response->ip_data_len + len > expected_len) { CFISCSI_SESSION_DEBUG(cs, "truncating from %zd " "to expected data transfer length %zd", buffer_offset + response->ip_data_len + len, expected_len); len = expected_len - (buffer_offset + response->ip_data_len); KASSERT(len <= sg_len, ("len %zd > sg_len %zd", len, sg_len)); } error = icl_pdu_append_data(response, sg_addr, len, M_NOWAIT); if (error != 0) { CFISCSI_SESSION_WARN(cs, "failed to " "allocate memory; dropping connection"); icl_pdu_free(response); ctl_set_busy(&io->scsiio); io->scsiio.be_move_done(io); cfiscsi_session_terminate(cs); return; } sg_addr += len; sg_len -= len; KASSERT(buffer_offset + response->ip_data_len <= expected_len, ("buffer_offset %zd + ip_data_len %zd > expected_len %zd", buffer_offset, response->ip_data_len, expected_len)); if (buffer_offset + response->ip_data_len == expected_len) { /* * Already have the amount of data the initiator wanted. */ break; } if (sg_len == 0) { /* * End of scatter-gather segment; * proceed to the next one... */ if (i == ctl_sg_count - 1) { /* * ... unless this was the last one. */ break; } i++; } if (response->ip_data_len == cs->cs_max_data_segment_length) { /* * Can't stuff more data into the current PDU; * queue it. Note that's not enough to check * for kern_data_resid == 0 instead; there * may be several Data-In PDUs for the final * call to cfiscsi_datamove(), and we want * to set the F flag only on the last of them. */ buffer_offset += response->ip_data_len; if (buffer_offset == io->scsiio.kern_total_len || buffer_offset == expected_len) bhsdi->bhsdi_flags |= BHSDI_FLAGS_F; cfiscsi_pdu_queue(response); response = NULL; bhsdi = NULL; } } if (response != NULL) { buffer_offset += response->ip_data_len; if (buffer_offset == io->scsiio.kern_total_len || buffer_offset == expected_len) bhsdi->bhsdi_flags |= BHSDI_FLAGS_F; KASSERT(response->ip_data_len > 0, ("sending empty Data-In")); cfiscsi_pdu_queue(response); } io->scsiio.be_move_done(io); } static void cfiscsi_datamove_out(union ctl_io *io) { struct cfiscsi_session *cs; struct icl_pdu *request, *response; const struct iscsi_bhs_scsi_command *bhssc; struct iscsi_bhs_r2t *bhsr2t; struct cfiscsi_data_wait *cdw; struct ctl_sg_entry ctl_sg_entry, *ctl_sglist; uint32_t expected_len, r2t_off, r2t_len; uint32_t target_transfer_tag; bool done; request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; cs = PDU_SESSION(request); bhssc = (const struct iscsi_bhs_scsi_command *)request->ip_bhs; KASSERT((bhssc->bhssc_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == ISCSI_BHS_OPCODE_SCSI_COMMAND, ("bhssc->bhssc_opcode != ISCSI_BHS_OPCODE_SCSI_COMMAND")); /* * We need to record it so that we can properly report * underflow/underflow. */ PDU_TOTAL_TRANSFER_LEN(request) = io->scsiio.kern_total_len; /* * Report write underflow as error since CTL and backends don't * really support it, and SCSI does not tell how to do it right. */ expected_len = ntohl(bhssc->bhssc_expected_data_transfer_length); if (io->scsiio.kern_rel_offset + io->scsiio.kern_data_len > expected_len) { io->scsiio.io_hdr.port_status = 43; io->scsiio.be_move_done(io); return; } target_transfer_tag = atomic_fetchadd_32(&cs->cs_target_transfer_tag, 1); #if 0 CFISCSI_SESSION_DEBUG(cs, "expecting Data-Out with initiator " "task tag 0x%x, target transfer tag 0x%x", bhssc->bhssc_initiator_task_tag, target_transfer_tag); #endif cdw = uma_zalloc(cfiscsi_data_wait_zone, M_NOWAIT | M_ZERO); if (cdw == NULL) { CFISCSI_SESSION_WARN(cs, "failed to " "allocate memory; dropping connection"); ctl_set_busy(&io->scsiio); io->scsiio.be_move_done(io); cfiscsi_session_terminate(cs); return; } cdw->cdw_ctl_io = io; cdw->cdw_target_transfer_tag = target_transfer_tag; cdw->cdw_initiator_task_tag = bhssc->bhssc_initiator_task_tag; cdw->cdw_r2t_end = io->scsiio.kern_data_len; /* Set initial data pointer for the CDW respecting ext_data_filled. */ if (io->scsiio.kern_sg_entries > 0) { ctl_sglist = (struct ctl_sg_entry *)io->scsiio.kern_data_ptr; } else { ctl_sglist = &ctl_sg_entry; ctl_sglist->addr = io->scsiio.kern_data_ptr; ctl_sglist->len = io->scsiio.kern_data_len; } cdw->cdw_sg_index = 0; cdw->cdw_sg_addr = ctl_sglist[cdw->cdw_sg_index].addr; cdw->cdw_sg_len = ctl_sglist[cdw->cdw_sg_index].len; r2t_off = io->scsiio.ext_data_filled; while (r2t_off > 0) { if (r2t_off >= cdw->cdw_sg_len) { r2t_off -= cdw->cdw_sg_len; cdw->cdw_sg_index++; cdw->cdw_sg_addr = ctl_sglist[cdw->cdw_sg_index].addr; cdw->cdw_sg_len = ctl_sglist[cdw->cdw_sg_index].len; continue; } cdw->cdw_sg_addr += r2t_off; cdw->cdw_sg_len -= r2t_off; r2t_off = 0; } if (cs->cs_immediate_data && io->scsiio.kern_rel_offset + io->scsiio.ext_data_filled < icl_pdu_data_segment_length(request)) { done = cfiscsi_handle_data_segment(request, cdw); if (done) { uma_zfree(cfiscsi_data_wait_zone, cdw); io->scsiio.be_move_done(io); return; } } r2t_off = io->scsiio.kern_rel_offset + io->scsiio.ext_data_filled; r2t_len = MIN(io->scsiio.kern_data_len - io->scsiio.ext_data_filled, cs->cs_max_burst_length); cdw->cdw_r2t_end = io->scsiio.ext_data_filled + r2t_len; CFISCSI_SESSION_LOCK(cs); TAILQ_INSERT_TAIL(&cs->cs_waiting_for_data_out, cdw, cdw_next); CFISCSI_SESSION_UNLOCK(cs); /* * XXX: We should limit the number of outstanding R2T PDUs * per task to MaxOutstandingR2T. */ response = cfiscsi_pdu_new_response(request, M_NOWAIT); if (response == NULL) { CFISCSI_SESSION_WARN(cs, "failed to " "allocate memory; dropping connection"); ctl_set_busy(&io->scsiio); io->scsiio.be_move_done(io); cfiscsi_session_terminate(cs); return; } bhsr2t = (struct iscsi_bhs_r2t *)response->ip_bhs; bhsr2t->bhsr2t_opcode = ISCSI_BHS_OPCODE_R2T; bhsr2t->bhsr2t_flags = 0x80; bhsr2t->bhsr2t_lun = bhssc->bhssc_lun; bhsr2t->bhsr2t_initiator_task_tag = bhssc->bhssc_initiator_task_tag; bhsr2t->bhsr2t_target_transfer_tag = target_transfer_tag; /* * XXX: Here we assume that cfiscsi_datamove() won't ever * be running concurrently on several CPUs for a given * command. */ bhsr2t->bhsr2t_r2tsn = htonl(PDU_R2TSN(request)); PDU_R2TSN(request)++; /* * This is the offset within the current SCSI command; * i.e. for the first call of datamove(), it will be 0, * and for subsequent ones it will be the sum of lengths * of previous ones. * * The ext_data_filled is to account for unsolicited * (immediate) data that might have already arrived. */ bhsr2t->bhsr2t_buffer_offset = htonl(r2t_off); /* * This is the total length (sum of S/G lengths) this call * to cfiscsi_datamove() is supposed to handle, limited by * MaxBurstLength. */ bhsr2t->bhsr2t_desired_data_transfer_length = htonl(r2t_len); cfiscsi_pdu_queue(response); } static void cfiscsi_datamove(union ctl_io *io) { if ((io->io_hdr.flags & CTL_FLAG_DATA_MASK) == CTL_FLAG_DATA_IN) cfiscsi_datamove_in(io); else { /* We hadn't received anything during this datamove yet. */ io->scsiio.ext_data_filled = 0; cfiscsi_datamove_out(io); } } static void cfiscsi_scsi_command_done(union ctl_io *io) { struct icl_pdu *request, *response; struct iscsi_bhs_scsi_command *bhssc; struct iscsi_bhs_scsi_response *bhssr; #ifdef DIAGNOSTIC struct cfiscsi_data_wait *cdw; #endif struct cfiscsi_session *cs; uint16_t sense_length; request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; cs = PDU_SESSION(request); bhssc = (struct iscsi_bhs_scsi_command *)request->ip_bhs; KASSERT((bhssc->bhssc_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == ISCSI_BHS_OPCODE_SCSI_COMMAND, ("replying to wrong opcode 0x%x", bhssc->bhssc_opcode)); //CFISCSI_SESSION_DEBUG(cs, "initiator task tag 0x%x", // bhssc->bhssc_initiator_task_tag); #ifdef DIAGNOSTIC CFISCSI_SESSION_LOCK(cs); TAILQ_FOREACH(cdw, &cs->cs_waiting_for_data_out, cdw_next) KASSERT(bhssc->bhssc_initiator_task_tag != cdw->cdw_initiator_task_tag, ("dangling cdw")); CFISCSI_SESSION_UNLOCK(cs); #endif /* * Do not return status for aborted commands. * There are exceptions, but none supported by CTL yet. */ if ((io->io_hdr.flags & CTL_FLAG_ABORT) && (io->io_hdr.flags & CTL_FLAG_ABORT_STATUS) == 0) { ctl_free_io(io); icl_pdu_free(request); return; } response = cfiscsi_pdu_new_response(request, M_WAITOK); bhssr = (struct iscsi_bhs_scsi_response *)response->ip_bhs; bhssr->bhssr_opcode = ISCSI_BHS_OPCODE_SCSI_RESPONSE; bhssr->bhssr_flags = 0x80; /* * XXX: We don't deal with bidirectional under/overflows; * does anything actually support those? */ if (PDU_TOTAL_TRANSFER_LEN(request) < ntohl(bhssc->bhssc_expected_data_transfer_length)) { bhssr->bhssr_flags |= BHSSR_FLAGS_RESIDUAL_UNDERFLOW; bhssr->bhssr_residual_count = htonl(ntohl(bhssc->bhssc_expected_data_transfer_length) - PDU_TOTAL_TRANSFER_LEN(request)); //CFISCSI_SESSION_DEBUG(cs, "underflow; residual count %d", // ntohl(bhssr->bhssr_residual_count)); } else if (PDU_TOTAL_TRANSFER_LEN(request) > ntohl(bhssc->bhssc_expected_data_transfer_length)) { bhssr->bhssr_flags |= BHSSR_FLAGS_RESIDUAL_OVERFLOW; bhssr->bhssr_residual_count = htonl(PDU_TOTAL_TRANSFER_LEN(request) - ntohl(bhssc->bhssc_expected_data_transfer_length)); //CFISCSI_SESSION_DEBUG(cs, "overflow; residual count %d", // ntohl(bhssr->bhssr_residual_count)); } bhssr->bhssr_response = BHSSR_RESPONSE_COMMAND_COMPLETED; bhssr->bhssr_status = io->scsiio.scsi_status; bhssr->bhssr_initiator_task_tag = bhssc->bhssc_initiator_task_tag; bhssr->bhssr_expdatasn = htonl(PDU_EXPDATASN(request)); if (io->scsiio.sense_len > 0) { #if 0 CFISCSI_SESSION_DEBUG(cs, "returning %d bytes of sense data", io->scsiio.sense_len); #endif sense_length = htons(io->scsiio.sense_len); icl_pdu_append_data(response, &sense_length, sizeof(sense_length), M_WAITOK); icl_pdu_append_data(response, &io->scsiio.sense_data, io->scsiio.sense_len, M_WAITOK); } ctl_free_io(io); icl_pdu_free(request); cfiscsi_pdu_queue(response); } static void cfiscsi_task_management_done(union ctl_io *io) { struct icl_pdu *request, *response; struct iscsi_bhs_task_management_request *bhstmr; struct iscsi_bhs_task_management_response *bhstmr2; struct cfiscsi_data_wait *cdw, *tmpcdw; struct cfiscsi_session *cs; request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; cs = PDU_SESSION(request); bhstmr = (struct iscsi_bhs_task_management_request *)request->ip_bhs; KASSERT((bhstmr->bhstmr_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == ISCSI_BHS_OPCODE_TASK_REQUEST, ("replying to wrong opcode 0x%x", bhstmr->bhstmr_opcode)); #if 0 CFISCSI_SESSION_DEBUG(cs, "initiator task tag 0x%x; referenced task tag 0x%x", bhstmr->bhstmr_initiator_task_tag, bhstmr->bhstmr_referenced_task_tag); #endif if ((bhstmr->bhstmr_function & ~0x80) == BHSTMR_FUNCTION_ABORT_TASK) { /* * Make sure we no longer wait for Data-Out for this command. */ CFISCSI_SESSION_LOCK(cs); TAILQ_FOREACH_SAFE(cdw, &cs->cs_waiting_for_data_out, cdw_next, tmpcdw) { if (bhstmr->bhstmr_referenced_task_tag != cdw->cdw_initiator_task_tag) continue; #if 0 CFISCSI_SESSION_DEBUG(cs, "removing csw for initiator task " "tag 0x%x", bhstmr->bhstmr_initiator_task_tag); #endif TAILQ_REMOVE(&cs->cs_waiting_for_data_out, cdw, cdw_next); cdw->cdw_ctl_io->scsiio.be_move_done(cdw->cdw_ctl_io); uma_zfree(cfiscsi_data_wait_zone, cdw); } CFISCSI_SESSION_UNLOCK(cs); } response = cfiscsi_pdu_new_response(request, M_WAITOK); bhstmr2 = (struct iscsi_bhs_task_management_response *) response->ip_bhs; bhstmr2->bhstmr_opcode = ISCSI_BHS_OPCODE_TASK_RESPONSE; bhstmr2->bhstmr_flags = 0x80; if (io->io_hdr.status == CTL_SUCCESS) { bhstmr2->bhstmr_response = BHSTMR_RESPONSE_FUNCTION_COMPLETE; } else { /* * XXX: How to figure out what exactly went wrong? iSCSI spec * expects us to provide detailed error, e.g. "Task does * not exist" or "LUN does not exist". */ CFISCSI_SESSION_DEBUG(cs, "BHSTMR_RESPONSE_FUNCTION_NOT_SUPPORTED"); bhstmr2->bhstmr_response = BHSTMR_RESPONSE_FUNCTION_NOT_SUPPORTED; } bhstmr2->bhstmr_initiator_task_tag = bhstmr->bhstmr_initiator_task_tag; ctl_free_io(io); icl_pdu_free(request); cfiscsi_pdu_queue(response); } static void cfiscsi_done(union ctl_io *io) { struct icl_pdu *request; struct cfiscsi_session *cs; KASSERT(((io->io_hdr.status & CTL_STATUS_MASK) != CTL_STATUS_NONE), ("invalid CTL status %#x", io->io_hdr.status)); if (io->io_hdr.io_type == CTL_IO_TASK && io->taskio.task_action == CTL_TASK_I_T_NEXUS_RESET) { /* * Implicit task termination has just completed; nothing to do. */ cs = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; cs->cs_tasks_aborted = true; refcount_release(&cs->cs_outstanding_ctl_pdus); wakeup(__DEVOLATILE(void *, &cs->cs_outstanding_ctl_pdus)); ctl_free_io(io); return; } request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; cs = PDU_SESSION(request); refcount_release(&cs->cs_outstanding_ctl_pdus); switch (request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) { case ISCSI_BHS_OPCODE_SCSI_COMMAND: cfiscsi_scsi_command_done(io); break; case ISCSI_BHS_OPCODE_TASK_REQUEST: cfiscsi_task_management_done(io); break; default: panic("cfiscsi_done called with wrong opcode 0x%x", request->ip_bhs->bhs_opcode); } } Index: projects/sendfile/sys/cddl/contrib/opensolaris/uts/common/sys/isa_defs.h =================================================================== --- projects/sendfile/sys/cddl/contrib/opensolaris/uts/common/sys/isa_defs.h (revision 274716) +++ projects/sendfile/sys/cddl/contrib/opensolaris/uts/common/sys/isa_defs.h (revision 274717) @@ -1,607 +1,609 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _SYS_ISA_DEFS_H #define _SYS_ISA_DEFS_H /* * This header file serves to group a set of well known defines and to * set these for each instruction set architecture. These defines may * be divided into two groups; characteristics of the processor and * implementation choices for Solaris on a processor. * * Processor Characteristics: * * _LITTLE_ENDIAN / _BIG_ENDIAN: * The natural byte order of the processor. A pointer to an int points * to the least/most significant byte of that int. * * _STACK_GROWS_UPWARD / _STACK_GROWS_DOWNWARD: * The processor specific direction of stack growth. A push onto the * stack increases/decreases the stack pointer, so it stores data at * successively higher/lower addresses. (Stackless machines ignored * without regrets). * * _LONG_LONG_HTOL / _LONG_LONG_LTOH: * A pointer to a long long points to the most/least significant long * within that long long. * * _BIT_FIELDS_HTOL / _BIT_FIELDS_LTOH: * The C compiler assigns bit fields from the high/low to the low/high end * of an int (most to least significant vs. least to most significant). * * _IEEE_754: * The processor (or supported implementations of the processor) * supports the ieee-754 floating point standard. No other floating * point standards are supported (or significant). Any other supported * floating point formats are expected to be cased on the ISA processor * symbol. * * _CHAR_IS_UNSIGNED / _CHAR_IS_SIGNED: * The C Compiler implements objects of type `char' as `unsigned' or * `signed' respectively. This is really an implementation choice of * the compiler writer, but it is specified in the ABI and tends to * be uniform across compilers for an instruction set architecture. * Hence, it has the properties of a processor characteristic. * * _CHAR_ALIGNMENT / _SHORT_ALIGNMENT / _INT_ALIGNMENT / _LONG_ALIGNMENT / * _LONG_LONG_ALIGNMENT / _DOUBLE_ALIGNMENT / _LONG_DOUBLE_ALIGNMENT / * _POINTER_ALIGNMENT / _FLOAT_ALIGNMENT: * The ABI defines alignment requirements of each of the primitive * object types. Some, if not all, may be hardware requirements as * well. The values are expressed in "byte-alignment" units. * * _MAX_ALIGNMENT: * The most stringent alignment requirement as specified by the ABI. * Equal to the maximum of all the above _XXX_ALIGNMENT values. * * _ALIGNMENT_REQUIRED: * True or false (1 or 0) whether or not the hardware requires the ABI * alignment. * * _LONG_LONG_ALIGNMENT_32 * The 32-bit ABI supported by a 64-bit kernel may have different * alignment requirements for primitive object types. The value of this * identifier is expressed in "byte-alignment" units. * * _HAVE_CPUID_INSN * This indicates that the architecture supports the 'cpuid' * instruction as defined by Intel. (Intel allows other vendors * to extend the instruction for their own purposes.) * * * Implementation Choices: * * _ILP32 / _LP64: * This specifies the compiler data type implementation as specified in * the relevant ABI. The choice between these is strongly influenced * by the underlying hardware, but is not absolutely tied to it. * Currently only two data type models are supported: * * _ILP32: * Int/Long/Pointer are 32 bits. This is the historical UNIX * and Solaris implementation. Due to its historical standing, * this is the default case. * * _LP64: * Long/Pointer are 64 bits, Int is 32 bits. This is the chosen * implementation for 64-bit ABIs such as SPARC V9. * * _I32LPx: * A compilation environment where 'int' is 32-bit, and * longs and pointers are simply the same size. * * In all cases, Char is 8 bits and Short is 16 bits. * * _SUNOS_VTOC_8 / _SUNOS_VTOC_16 / _SVR4_VTOC_16: * This specifies the form of the disk VTOC (or label): * * _SUNOS_VTOC_8: * This is a VTOC form which is upwardly compatible with the * SunOS 4.x disk label and allows 8 partitions per disk. * * _SUNOS_VTOC_16: * In this format the incore vtoc image matches the ondisk * version. It allows 16 slices per disk, and is not * compatible with the SunOS 4.x disk label. * * Note that these are not the only two VTOC forms possible and * additional forms may be added. One possible form would be the * SVr4 VTOC form. The symbol for that is reserved now, although * it is not implemented. * * _SVR4_VTOC_16: * This VTOC form is compatible with the System V Release 4 * VTOC (as implemented on the SVr4 Intel and 3b ports) with * 16 partitions per disk. * * * _DMA_USES_PHYSADDR / _DMA_USES_VIRTADDR * This describes the type of addresses used by system DMA: * * _DMA_USES_PHYSADDR: * This type of DMA, used in the x86 implementation, * requires physical addresses for DMA buffers. The 24-bit * addresses used by some legacy boards is the source of the * "low-memory" (<16MB) requirement for some devices using DMA. * * _DMA_USES_VIRTADDR: * This method of DMA allows the use of virtual addresses for * DMA transfers. * * _FIRMWARE_NEEDS_FDISK / _NO_FDISK_PRESENT * This indicates the presence/absence of an fdisk table. * * _FIRMWARE_NEEDS_FDISK * The fdisk table is required by system firmware. If present, * it allows a disk to be subdivided into multiple fdisk * partitions, each of which is equivalent to a separate, * virtual disk. This enables the co-existence of multiple * operating systems on a shared hard disk. * * _NO_FDISK_PRESENT * If the fdisk table is absent, it is assumed that the entire * media is allocated for a single operating system. * * _HAVE_TEM_FIRMWARE * Defined if this architecture has the (fallback) option of * using prom_* calls for doing I/O if a suitable kernel driver * is not available to do it. * * _DONT_USE_1275_GENERIC_NAMES * Controls whether or not device tree node names should * comply with the IEEE 1275 "Generic Names" Recommended * Practice. With _DONT_USE_GENERIC_NAMES, device-specific * names identifying the particular device will be used. * * __i386_COMPAT * This indicates whether the i386 ABI is supported as a *non-native* * mode for the platform. When this symbol is defined: * - 32-bit xstat-style system calls are enabled * - 32-bit xmknod-style system calls are enabled * - 32-bit system calls use i386 sizes -and- alignments * * Note that this is NOT defined for the i386 native environment! * * __x86 * This is ONLY a synonym for defined(__i386) || defined(__amd64) * which is useful only insofar as these two architectures share * common attributes. Analogous to __sparc. * * _PSM_MODULES * This indicates whether or not the implementation uses PSM * modules for processor support, reading /etc/mach from inside * the kernel to extract a list. * * _RTC_CONFIG * This indicates whether or not the implementation uses /etc/rtc_config * to configure the real-time clock in the kernel. * * _UNIX_KRTLD * This indicates that the implementation uses a dynamically * linked unix + krtld to form the core kernel image at boot * time, or (in the absence of this symbol) a prelinked kernel image. * * _OBP * This indicates the firmware interface is OBP. * * _SOFT_HOSTID * This indicates that the implementation obtains the hostid * from the file /etc/hostid, rather than from hardware. */ #ifdef __cplusplus extern "C" { #endif /* * The following set of definitions characterize Solaris on AMD's * 64-bit systems. */ #if defined(__x86_64) || defined(__amd64) #if !defined(__amd64) #define __amd64 /* preferred guard */ #endif #if !defined(__x86) #define __x86 #endif /* * Define the appropriate "processor characteristics" */ #if defined(sun) #define _LITTLE_ENDIAN #endif #define _STACK_GROWS_DOWNWARD #define _LONG_LONG_LTOH #define _BIT_FIELDS_LTOH #define _IEEE_754 #define _CHAR_IS_SIGNED #define _BOOL_ALIGNMENT 1 #define _CHAR_ALIGNMENT 1 #define _SHORT_ALIGNMENT 2 #define _INT_ALIGNMENT 4 #define _FLOAT_ALIGNMENT 4 #define _FLOAT_COMPLEX_ALIGNMENT 4 #define _LONG_ALIGNMENT 8 #define _LONG_LONG_ALIGNMENT 8 #define _DOUBLE_ALIGNMENT 8 #define _DOUBLE_COMPLEX_ALIGNMENT 8 #define _LONG_DOUBLE_ALIGNMENT 16 #define _LONG_DOUBLE_COMPLEX_ALIGNMENT 16 #define _POINTER_ALIGNMENT 8 #define _MAX_ALIGNMENT 16 #define _ALIGNMENT_REQUIRED 1 /* * Different alignment constraints for the i386 ABI in compatibility mode */ #define _LONG_LONG_ALIGNMENT_32 4 /* * Define the appropriate "implementation choices". */ #if !defined(_LP64) #define _LP64 #endif #if !defined(_I32LPx) && defined(_KERNEL) #define _I32LPx #endif #define _MULTI_DATAMODEL #define _SUNOS_VTOC_16 #define _DMA_USES_PHYSADDR #define _FIRMWARE_NEEDS_FDISK #define __i386_COMPAT #define _PSM_MODULES #define _RTC_CONFIG #define _SOFT_HOSTID #define _DONT_USE_1275_GENERIC_NAMES #define _HAVE_CPUID_INSN /* * The feature test macro __i386 is generic for all processors implementing * the Intel 386 instruction set or a superset of it. Specifically, this * includes all members of the 386, 486, and Pentium family of processors. */ #elif defined(__i386) || defined(__i386__) #if !defined(__i386) #define __i386 #endif #if !defined(__x86) #define __x86 #endif /* * Define the appropriate "processor characteristics" */ #if defined(sun) #define _LITTLE_ENDIAN #endif #define _STACK_GROWS_DOWNWARD #define _LONG_LONG_LTOH #define _BIT_FIELDS_LTOH #define _IEEE_754 #define _CHAR_IS_SIGNED #define _BOOL_ALIGNMENT 1 #define _CHAR_ALIGNMENT 1 #define _SHORT_ALIGNMENT 2 #define _INT_ALIGNMENT 4 #define _FLOAT_ALIGNMENT 4 #define _FLOAT_COMPLEX_ALIGNMENT 4 #define _LONG_ALIGNMENT 4 #define _LONG_LONG_ALIGNMENT 4 #define _DOUBLE_ALIGNMENT 4 #define _DOUBLE_COMPLEX_ALIGNMENT 4 #define _LONG_DOUBLE_ALIGNMENT 4 #define _LONG_DOUBLE_COMPLEX_ALIGNMENT 4 #define _POINTER_ALIGNMENT 4 #define _MAX_ALIGNMENT 4 #define _ALIGNMENT_REQUIRED 0 #define _LONG_LONG_ALIGNMENT_32 _LONG_LONG_ALIGNMENT /* * Define the appropriate "implementation choices". */ +#if !defined(_ILP32) #define _ILP32 +#endif #if !defined(_I32LPx) && defined(_KERNEL) #define _I32LPx #endif #define _SUNOS_VTOC_16 #define _DMA_USES_PHYSADDR #define _FIRMWARE_NEEDS_FDISK #define _PSM_MODULES #define _RTC_CONFIG #define _SOFT_HOSTID #define _DONT_USE_1275_GENERIC_NAMES #define _HAVE_CPUID_INSN #elif defined(__arm__) /* * Define the appropriate "processor characteristics" */ #define _STACK_GROWS_DOWNWARD #define _LONG_LONG_LTOH #define _BIT_FIELDS_LTOH #define _IEEE_754 #define _CHAR_IS_SIGNED #define _BOOL_ALIGNMENT 1 #define _CHAR_ALIGNMENT 1 #define _SHORT_ALIGNMENT 2 #define _INT_ALIGNMENT 4 #define _FLOAT_ALIGNMENT 4 #define _FLOAT_COMPLEX_ALIGNMENT 4 #define _LONG_ALIGNMENT 4 #define _LONG_LONG_ALIGNMENT 4 #define _DOUBLE_ALIGNMENT 4 #define _DOUBLE_COMPLEX_ALIGNMENT 4 #define _LONG_DOUBLE_ALIGNMENT 4 #define _LONG_DOUBLE_COMPLEX_ALIGNMENT 4 #define _POINTER_ALIGNMENT 4 #define _MAX_ALIGNMENT 4 #define _ALIGNMENT_REQUIRED 0 #define _LONG_LONG_ALIGNMENT_32 _LONG_LONG_ALIGNMENT /* * Define the appropriate "implementation choices". */ #define _ILP32 #if !defined(_I32LPx) && defined(_KERNEL) #define _I32LPx #endif #define _SUNOS_VTOC_16 #define _DMA_USES_PHYSADDR #define _FIRMWARE_NEEDS_FDISK #define _PSM_MODULES #define _RTC_CONFIG #define _DONT_USE_1275_GENERIC_NAMES #define _HAVE_CPUID_INSN #elif defined(__mips__) /* * Define the appropriate "processor characteristics" */ #define _STACK_GROWS_DOWNWARD #define _LONG_LONG_LTOH #define _BIT_FIELDS_LTOH #define _IEEE_754 #define _CHAR_IS_SIGNED #define _BOOL_ALIGNMENT 1 #define _CHAR_ALIGNMENT 1 #define _SHORT_ALIGNMENT 2 #define _INT_ALIGNMENT 4 #define _FLOAT_ALIGNMENT 4 #define _FLOAT_COMPLEX_ALIGNMENT 4 #if defined(__mips_n64) #define _LONG_ALIGNMENT 8 #define _LONG_LONG_ALIGNMENT 8 #define _DOUBLE_ALIGNMENT 8 #define _DOUBLE_COMPLEX_ALIGNMENT 8 #define _LONG_DOUBLE_ALIGNMENT 8 #define _LONG_DOUBLE_COMPLEX_ALIGNMENT 8 #define _POINTER_ALIGNMENT 8 #define _MAX_ALIGNMENT 8 #define _ALIGNMENT_REQUIRED 0 #define _LONG_LONG_ALIGNMENT_32 _INT_ALIGNMENT /* * Define the appropriate "implementation choices". */ #if !defined(_LP64) #define _LP64 #endif #else #define _LONG_ALIGNMENT 4 #define _LONG_LONG_ALIGNMENT 4 #define _DOUBLE_ALIGNMENT 4 #define _DOUBLE_COMPLEX_ALIGNMENT 4 #define _LONG_DOUBLE_ALIGNMENT 4 #define _LONG_DOUBLE_COMPLEX_ALIGNMENT 4 #define _POINTER_ALIGNMENT 4 #define _MAX_ALIGNMENT 4 #define _ALIGNMENT_REQUIRED 0 #define _LONG_LONG_ALIGNMENT_32 _LONG_LONG_ALIGNMENT /* * Define the appropriate "implementation choices". */ #define _ILP32 #if !defined(_I32LPx) && defined(_KERNEL) #define _I32LPx #endif #endif #define _SUNOS_VTOC_16 #define _DMA_USES_PHYSADDR #define _FIRMWARE_NEEDS_FDISK #define _PSM_MODULES #define _RTC_CONFIG #define _DONT_USE_1275_GENERIC_NAMES #define _HAVE_CPUID_INSN #elif defined(__powerpc__) #if defined(__BIG_ENDIAN__) #define _BIT_FIELDS_HTOL #else #define _BIT_FIELDS_LTOH #endif /* * The following set of definitions characterize the Solaris on SPARC systems. * * The symbol __sparc indicates any of the SPARC family of processor * architectures. This includes SPARC V7, SPARC V8 and SPARC V9. * * The symbol __sparcv8 indicates the 32-bit SPARC V8 architecture as defined * by Version 8 of the SPARC Architecture Manual. (SPARC V7 is close enough * to SPARC V8 for the former to be subsumed into the latter definition.) * * The symbol __sparcv9 indicates the 64-bit SPARC V9 architecture as defined * by Version 9 of the SPARC Architecture Manual. * * The symbols __sparcv8 and __sparcv9 are mutually exclusive, and are only * relevant when the symbol __sparc is defined. */ /* * XXX Due to the existence of 5110166, "defined(__sparcv9)" needs to be added * to support backwards builds. This workaround should be removed in s10_71. */ #elif defined(__sparc) || defined(__sparcv9) || defined(__sparc__) #if !defined(__sparc) #define __sparc #endif /* * You can be 32-bit or 64-bit, but not both at the same time. */ #if defined(__sparcv8) && defined(__sparcv9) #error "SPARC Versions 8 and 9 are mutually exclusive choices" #endif /* * Existing compilers do not set __sparcv8. Years will transpire before * the compilers can be depended on to set the feature test macro. In * the interim, we'll set it here on the basis of historical behaviour; * if you haven't asked for SPARC V9, then you must've meant SPARC V8. */ #if !defined(__sparcv9) && !defined(__sparcv8) #define __sparcv8 #endif /* * Define the appropriate "processor characteristics" shared between * all Solaris on SPARC systems. */ #if defined(sun) #define _BIG_ENDIAN #endif #define _STACK_GROWS_DOWNWARD #define _LONG_LONG_HTOL #define _BIT_FIELDS_HTOL #define _IEEE_754 #define _CHAR_IS_SIGNED #define _BOOL_ALIGNMENT 1 #define _CHAR_ALIGNMENT 1 #define _SHORT_ALIGNMENT 2 #define _INT_ALIGNMENT 4 #define _FLOAT_ALIGNMENT 4 #define _FLOAT_COMPLEX_ALIGNMENT 4 #define _LONG_LONG_ALIGNMENT 8 #define _DOUBLE_ALIGNMENT 8 #define _DOUBLE_COMPLEX_ALIGNMENT 8 #define _ALIGNMENT_REQUIRED 1 /* * Define the appropriate "implementation choices" shared between versions. */ #define _SUNOS_VTOC_8 #define _DMA_USES_VIRTADDR #define _NO_FDISK_PRESENT #define _HAVE_TEM_FIRMWARE #define _OBP /* * The following set of definitions characterize the implementation of * 32-bit Solaris on SPARC V8 systems. */ #if defined(__sparcv8) /* * Define the appropriate "processor characteristics" */ #define _LONG_ALIGNMENT 4 #define _LONG_DOUBLE_ALIGNMENT 8 #define _LONG_DOUBLE_COMPLEX_ALIGNMENT 8 #define _POINTER_ALIGNMENT 4 #define _MAX_ALIGNMENT 8 #define _LONG_LONG_ALIGNMENT_32 _LONG_LONG_ALIGNMENT /* * Define the appropriate "implementation choices" */ #define _ILP32 #if !defined(_I32LPx) && defined(_KERNEL) #define _I32LPx #endif /* * The following set of definitions characterize the implementation of * 64-bit Solaris on SPARC V9 systems. */ #elif defined(__sparcv9) /* * Define the appropriate "processor characteristics" */ #define _LONG_ALIGNMENT 8 #define _LONG_DOUBLE_ALIGNMENT 16 #define _LONG_DOUBLE_COMPLEX_ALIGNMENT 16 #define _POINTER_ALIGNMENT 8 #define _MAX_ALIGNMENT 16 #define _LONG_LONG_ALIGNMENT_32 _LONG_LONG_ALIGNMENT /* * Define the appropriate "implementation choices" */ #if !defined(_LP64) #define _LP64 #endif #if !defined(_I32LPx) #define _I32LPx #endif #define _MULTI_DATAMODEL #else #error "unknown SPARC version" #endif /* * #error is strictly ansi-C, but works as well as anything for K&R systems. */ #else #error "ISA not supported" #endif #if defined(_ILP32) && defined(_LP64) #error "Both _ILP32 and _LP64 are defined" #endif #ifdef __cplusplus } #endif #endif /* _SYS_ISA_DEFS_H */ Index: projects/sendfile/sys/cddl/contrib/opensolaris =================================================================== --- projects/sendfile/sys/cddl/contrib/opensolaris (revision 274716) +++ projects/sendfile/sys/cddl/contrib/opensolaris (revision 274717) Property changes on: projects/sendfile/sys/cddl/contrib/opensolaris ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head/sys/cddl/contrib/opensolaris:r274690-274716 Index: projects/sendfile/sys/contrib/ngatm/netnatm/saal/saal_sscop.c =================================================================== --- projects/sendfile/sys/contrib/ngatm/netnatm/saal/saal_sscop.c (revision 274716) +++ projects/sendfile/sys/contrib/ngatm/netnatm/saal/saal_sscop.c (revision 274717) @@ -1,4947 +1,4946 @@ /* * Copyright (c) 1996-2003 * Fraunhofer Institute for Open Communication Systems (FhG Fokus). * All rights reserved. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * Author: Hartmut Brandt * * $Begemot: libunimsg/netnatm/saal/saal_sscop.c,v 1.11 2004/07/08 08:22:13 brandt Exp $ * * Core SSCOP code (ITU-T Q.2110) */ #include #include #ifndef FAILURE #define FAILURE(S) #endif #define MKSTR(S) #S static const char *const sscop_sigs[] = { MKSTR(SSCOP_ESTABLISH_request), MKSTR(SSCOP_ESTABLISH_indication), MKSTR(SSCOP_ESTABLISH_response), MKSTR(SSCOP_ESTABLISH_confirm), MKSTR(SSCOP_RELEASE_request), MKSTR(SSCOP_RELEASE_indication), MKSTR(SSCOP_RELEASE_confirm), MKSTR(SSCOP_DATA_request), MKSTR(SSCOP_DATA_indication), MKSTR(SSCOP_UDATA_request), MKSTR(SSCOP_UDATA_indication), MKSTR(SSCOP_RECOVER_indication), MKSTR(SSCOP_RECOVER_response), MKSTR(SSCOP_RESYNC_request), MKSTR(SSCOP_RESYNC_indication), MKSTR(SSCOP_RESYNC_response), MKSTR(SSCOP_RESYNC_confirm), MKSTR(SSCOP_RETRIEVE_request), MKSTR(SSCOP_RETRIEVE_indication), MKSTR(SSCOP_RETRIEVE_COMPL_indication), }; static const char *const sscop_msigs[] = { MKSTR(SSCOP_MDATA_request), MKSTR(SSCOP_MDATA_indication), MKSTR(SSCOP_MERROR_indication), }; static const char *const states[] = { MKSTR(SSCOP_IDLE), MKSTR(SSCOP_OUT_PEND), MKSTR(SSCOP_IN_PEND), MKSTR(SSCOP_OUT_DIS_PEND), MKSTR(SSCOP_OUT_RESYNC_PEND), MKSTR(SSCOP_IN_RESYNC_PEND), MKSTR(SSCOP_OUT_REC_PEND), MKSTR(SSCOP_REC_PEND), MKSTR(SSCOP_IN_REC_PEND), MKSTR(SSCOP_READY), }; #ifdef SSCOP_DEBUG static const char *const events[] = { MKSTR(SIG_BGN), MKSTR(SIG_BGAK), MKSTR(SIG_END), MKSTR(SIG_ENDAK), MKSTR(SIG_RS), MKSTR(SIG_RSAK), MKSTR(SIG_BGREJ), MKSTR(SIG_SD), MKSTR(SIG_ER), MKSTR(SIG_POLL), MKSTR(SIG_STAT), MKSTR(SIG_USTAT), MKSTR(SIG_UD), MKSTR(SIG_MD), MKSTR(SIG_ERAK), MKSTR(SIG_T_CC), MKSTR(SIG_T_POLL), MKSTR(SIG_T_KA), MKSTR(SIG_T_NR), MKSTR(SIG_T_IDLE), MKSTR(SIG_PDU_Q), MKSTR(SIG_USER_DATA), MKSTR(SIG_ESTAB_REQ), MKSTR(SIG_ESTAB_RESP), MKSTR(SIG_RELEASE_REQ), MKSTR(SIG_RECOVER), MKSTR(SIG_SYNC_REQ), MKSTR(SIG_SYNC_RESP), MKSTR(SIG_UDATA), MKSTR(SIG_MDATA), MKSTR(SIG_UPDU_Q), MKSTR(SIG_MPDU_Q), MKSTR(SIG_RETRIEVE), }; static const char *const pdus[] = { "illegale PDU type 0", /* no PDU type 0 */ MKSTR(PDU_BGN), MKSTR(PDU_BGAK), MKSTR(PDU_END), MKSTR(PDU_ENDAK), MKSTR(PDU_RS), MKSTR(PDU_RSAK), MKSTR(PDU_BGREJ), MKSTR(PDU_SD), MKSTR(PDU_ER), MKSTR(PDU_POLL), MKSTR(PDU_STAT), MKSTR(PDU_USTAT), MKSTR(PDU_UD), MKSTR(PDU_MD), MKSTR(PDU_ERAK), }; #endif MEMINIT(); static void sscop_signal(struct sscop *, u_int, struct sscop_msg *); static void sscop_save_signal(struct sscop *, u_int, struct sscop_msg *); static void handle_sigs(struct sscop *); static void sscop_set_state(struct sscop *, u_int); /************************************************************/ /************************************************************/ /* * Queue macros */ #define SSCOP_MSG_FREE(MSG) \ do { \ if(MSG) { \ MBUF_FREE((MSG)->m); \ MSG_FREE((MSG)); \ } \ } while(0) - -#define QFIND(Q,RN) \ - ({ \ - struct sscop_msg *_msg = NULL, *_m; \ - MSGQ_FOREACH(_m, (Q)) { \ - if(_m->seqno == (RN)) { \ - _msg = _m; \ - break; \ - } \ - } \ - _msg; \ - }) +static inline struct sscop_msg *QFIND(sscop_msgq_head_t *q, u_int rn) +{ + struct sscop_msg *msg = NULL, *m; + MSGQ_FOREACH(m, q) { + if(m->seqno == rn) { + msg = m; + break; + } + } + return msg; +} #define QINSERT(Q,M) \ do { \ struct sscop_msg *_msg = NULL, *_m; \ MSGQ_FOREACH(_m, (Q)) { \ if (_m->seqno > (M)->seqno) { \ _msg = _m; \ break; \ } \ } \ if (_msg != NULL) \ MSGQ_INSERT_BEFORE(_msg, (M)); \ else \ MSGQ_APPEND((Q), (M)); \ } while (0) /* * Send an error indication to the management plane. */ #define MAAL_ERROR(S,E,C) \ do { \ VERBOSE(S, SSCOP_DBG_USIG, ((S), (S)->aarg, \ "MAA-Signal %s in state %s", \ sscop_msigs[SSCOP_MERROR_indication], states[(S)->state])); \ (S)->funcs->send_manage((S), (S)->aarg, \ SSCOP_MERROR_indication, NULL, (E), (C)); \ } while(0) #define MAAL_DATA(S,M) \ do { \ VERBOSE(S, SSCOP_DBG_USIG, ((S), (S)->aarg, \ "MAA-Signal %s in state %s", \ sscop_msigs[SSCOP_MDATA_indication], states[(S)->state])); \ (S)->funcs->send_manage((S), (S)->aarg, \ SSCOP_MDATA_indication, (M), 0, 0); \ } while(0) #define AAL_DATA(S,D,M,N) \ do { \ VERBOSE(S, SSCOP_DBG_USIG, ((S), (S)->aarg, \ "AA-Signal %s in state %s", \ sscop_sigs[D], states[(S)->state])); \ (S)->funcs->send_upper((S), (S)->aarg, (D), (M), (N)); \ } while(0) #define AAL_SIG(S,D) \ do { \ VERBOSE(S, SSCOP_DBG_USIG, ((S), (S)->aarg, \ "AA-Signal %s in state %s", \ sscop_sigs[D], states[(S)->state])); \ (S)->funcs->send_upper((S), (S)->aarg, (D), NULL, 0); \ } while(0) #ifdef SSCOP_DEBUG #define AAL_SEND(S,M) do { \ if (ISVERBOSE(S, SSCOP_DBG_PDU)) \ sscop_dump_pdu(S, "tx", (M)); \ (S)->funcs->send_lower((S), (S)->aarg, (M)); \ } while(0) #else #define AAL_SEND(S,M) (S)->funcs->send_lower((S), (S)->aarg, (M)) #endif /* * Free a save user-to-user data buffer and set the pointer to zero * to signal, that it is free. */ #define FREE_UU(F) \ do { \ if(sscop->F) { \ MBUF_FREE(sscop->F); \ sscop->F = NULL; \ } \ } while(0) #define SET_UU(F,U) \ do { \ FREE_UU(F); \ sscop->F = U->m; \ U->m = NULL; \ SSCOP_MSG_FREE(U); \ } while(0) #define AAL_UU_SIGNAL(S, SIG, M, PL, SN) \ do { \ if(MBUF_LEN((M)->m) > 0) { \ MBUF_UNPAD((M)->m,(PL)); \ AAL_DATA((S), (SIG), (M)->m, (SN)); \ (M)->m = NULL; \ } else { \ AAL_DATA((S), (SIG), NULL, (SN)); \ } \ SSCOP_MSG_FREE((M)); \ } while(0) TIMER_FUNC(cc, CC) TIMER_FUNC(nr, NR) TIMER_FUNC(ka, KA) TIMER_FUNC(poll, POLL) TIMER_FUNC(idle, IDLE) /************************************************************/ /* * INSTANCE AND TYPE HANDLING. */ #ifdef SSCOP_DEBUG static void sscop_dump_pdu(struct sscop *sscop, const char *dir, const struct SSCOP_MBUF_T *m) { u_int32_t v1, v2, v3, v4; u_int size = MBUF_LEN(m); u_int n, i; if (size < 8) return; v1 = MBUF_TRAIL32(m, -1); v2 = MBUF_TRAIL32(m, -2); switch ((v1 >> 24) & 0xf) { case 0: return; case PDU_BGN: sscop->funcs->verbose(sscop, sscop->aarg, "%s BGN n(mr)=%u n(sq)=%u pl=%u", dir, v1 & 0xffffff, v2 & 0xff, (v1 >> 30) & 0x3); return; case PDU_BGAK: sscop->funcs->verbose(sscop, sscop->aarg, "%s BGAK n(mr)=%u pl=%u", dir, v1 & 0xffffff, (v1 >> 30) & 0x3); return; case PDU_END: sscop->funcs->verbose(sscop, sscop->aarg, "%s END r=%u s=%u pl=%u", dir, (v1 >> 29) & 1, (v1 >> 28) & 1, (v1 >> 30) & 0x3); return; case PDU_ENDAK: sscop->funcs->verbose(sscop, sscop->aarg, "%s ENDAK", dir); return; case PDU_RS: sscop->funcs->verbose(sscop, sscop->aarg, "%s RS n(mr)=%u n(sq)=%u pl=%u", dir, v1 & 0xffffff, v2 & 0xff, (v1 >> 30) & 0x3); return; case PDU_RSAK: sscop->funcs->verbose(sscop, sscop->aarg, "%s RSAK n(mr)=%u", dir, v1 & 0xffffff); return; case PDU_BGREJ: sscop->funcs->verbose(sscop, sscop->aarg, "%s BGREJ pl=%u", dir, (v1 >> 30) & 0x3); return; case PDU_SD: sscop->funcs->verbose(sscop, sscop->aarg, "%s SD n(s)=%u pl=%u", dir, v1 & 0xffffff, (v1 >> 30) & 0x3); return; case PDU_ER: sscop->funcs->verbose(sscop, sscop->aarg, "%s ER n(mr)=%u n(sq)=%u", dir, v1 & 0xffffff, v2 & 0xff); return; case PDU_POLL: sscop->funcs->verbose(sscop, sscop->aarg, "%s POLL n(s)=%u n(ps)=%u", dir, v1 & 0xffffff, v2 & 0xffffff); return; case PDU_STAT: if (size < 12) return; v3 = MBUF_TRAIL32(m, -3); sscop->funcs->verbose(sscop, sscop->aarg, "%s STAT n(r)=%u n(mr)=%u n(ps)=%u", dir, v1 & 0xffffff, v2 & 0xffffff, v3 & 0xffffff); n = (size - 12) / 4; for (i = 0; i < (size - 12) / 4; i++, n--) { v4 = MBUF_TRAIL32(m, -4 - (int)i); sscop->funcs->verbose(sscop, sscop->aarg, " LE(%u)=%u", n, v4 & 0xffffff); } return; case PDU_USTAT: if (size < 16) return; sscop->funcs->verbose(sscop, sscop->aarg, "%s STAT n(r)=%u n(mr)=%u LE1=%u LE2=%u", dir, v1 & 0xffffff, v2 & 0xffffff, MBUF_TRAIL32(m, -4) & 0xffffff, MBUF_TRAIL32(m, -3) & 0xffffff); return; case PDU_UD: sscop->funcs->verbose(sscop, sscop->aarg, "%s UD pl=%u", dir, (v1 >> 30) & 0x3); return; case PDU_MD: sscop->funcs->verbose(sscop, sscop->aarg, "%s MD pl=%u", dir, (v1 >> 30) & 0x3); return; case PDU_ERAK: sscop->funcs->verbose(sscop, sscop->aarg, "%s ERAK n(mr)=%u", dir, v1 & 0xffffff); return; } } #endif /* * Initialize state of variables */ static void sscop_init(struct sscop *sscop) { sscop->state = SSCOP_IDLE; sscop->vt_sq = 0; sscop->vr_sq = 0; sscop->clear_buffers = 1; sscop->ll_busy = 0; sscop->rxq = 0; } static void sscop_clear(struct sscop *sscop) { TIMER_STOP(sscop, cc); TIMER_STOP(sscop, ka); TIMER_STOP(sscop, nr); TIMER_STOP(sscop, idle); TIMER_STOP(sscop, poll); FREE_UU(uu_bgn); FREE_UU(uu_bgak); FREE_UU(uu_bgrej); FREE_UU(uu_end); FREE_UU(uu_rs); MSGQ_CLEAR(&sscop->xq); MSGQ_CLEAR(&sscop->uxq); MSGQ_CLEAR(&sscop->mxq); MSGQ_CLEAR(&sscop->xbuf); MSGQ_CLEAR(&sscop->rbuf); SIGQ_CLEAR(&sscop->sigs); SIGQ_CLEAR(&sscop->saved_sigs); } /* * Allocate instance memory, initialize the state of all variables. */ struct sscop * sscop_create(void *a, const struct sscop_funcs *funcs) { struct sscop *sscop; MEMZALLOC(sscop, struct sscop *, sizeof(struct sscop)); if (sscop == NULL) return (NULL); if (a == NULL) sscop->aarg = sscop; else sscop->aarg = a; sscop->funcs = funcs; sscop->maxk = MAXK; sscop->maxj = MAXJ; sscop->maxcc = MAXCC; sscop->maxpd = MAXPD; sscop->maxstat = MAXSTAT; sscop->timercc = TIMERCC; sscop->timerka = TIMERKA; sscop->timernr = TIMERNR; sscop->timerpoll = TIMERPOLL; sscop->timeridle = TIMERIDLE; sscop->robustness = 0; sscop->poll_after_rex = 0; sscop->mr = MAXMR; TIMER_INIT(sscop, cc); TIMER_INIT(sscop, nr); TIMER_INIT(sscop, ka); TIMER_INIT(sscop, poll); TIMER_INIT(sscop, idle); MSGQ_INIT(&sscop->xq); MSGQ_INIT(&sscop->uxq); MSGQ_INIT(&sscop->mxq); MSGQ_INIT(&sscop->rbuf); MSGQ_INIT(&sscop->xbuf); SIGQ_INIT(&sscop->sigs); SIGQ_INIT(&sscop->saved_sigs); sscop_init(sscop); return (sscop); } /* * Free all resources in a sscop instance */ void sscop_destroy(struct sscop *sscop) { sscop_reset(sscop); MEMFREE(sscop); } /* * Reset the SSCOP instance. */ void sscop_reset(struct sscop *sscop) { sscop_clear(sscop); sscop_init(sscop); } void sscop_getparam(const struct sscop *sscop, struct sscop_param *p) { p->timer_cc = sscop->timercc; p->timer_poll = sscop->timerpoll; p->timer_keep_alive = sscop->timerka; p->timer_no_response = sscop->timernr; p->timer_idle = sscop->timeridle; p->maxk = sscop->maxk; p->maxj = sscop->maxj; p->maxcc = sscop->maxcc; p->maxpd = sscop->maxpd; p->maxstat = sscop->maxstat; p->mr = sscop->mr; p->flags = 0; if(sscop->robustness) p->flags |= SSCOP_ROBUST; if(sscop->poll_after_rex) p->flags |= SSCOP_POLLREX; } int sscop_setparam(struct sscop *sscop, struct sscop_param *p, u_int *pmask) { u_int mask = *pmask; /* can change only in idle state */ if (sscop->state != SSCOP_IDLE) return (EISCONN); *pmask = 0; /* * first check all parameters */ if ((mask & SSCOP_SET_TCC) && p->timer_cc == 0) *pmask |= SSCOP_SET_TCC; if ((mask & SSCOP_SET_TPOLL) && p->timer_poll == 0) *pmask |= SSCOP_SET_TPOLL; if ((mask & SSCOP_SET_TKA) && p->timer_keep_alive == 0) *pmask |= SSCOP_SET_TKA; if ((mask & SSCOP_SET_TNR) && p->timer_no_response == 0) *pmask |= SSCOP_SET_TNR; if ((mask & SSCOP_SET_TIDLE) && p->timer_idle == 0) *pmask |= SSCOP_SET_TIDLE; if ((mask & SSCOP_SET_MAXK) && p->maxk > MAXMAXK) *pmask |= SSCOP_SET_MAXK; if ((mask & SSCOP_SET_MAXJ) && p->maxj > MAXMAXJ) *pmask |= SSCOP_SET_MAXJ; if ((mask & SSCOP_SET_MAXCC) && p->maxcc > 255) *pmask |= SSCOP_SET_MAXCC; if ((mask & SSCOP_SET_MAXPD) && p->maxpd >= (1 << 24)) *pmask |= SSCOP_SET_MAXPD; if ((mask & SSCOP_SET_MAXSTAT) && ((p->maxstat & 1) == 0 || p->maxstat == 1 || p->maxstat == 2 || p->maxstat * 4 > MAXMAXK - 8)) *pmask |= SSCOP_SET_MAXSTAT; if ((mask & SSCOP_SET_MR) && p->mr >= (1 << 24) - 1) *pmask |= SSCOP_SET_MR; if (*pmask) return (EINVAL); /* * now set it */ if (mask & SSCOP_SET_TCC) sscop->timercc = p->timer_cc; if (mask & SSCOP_SET_TPOLL) sscop->timerpoll = p->timer_poll; if (mask & SSCOP_SET_TKA) sscop->timerka = p->timer_keep_alive; if (mask & SSCOP_SET_TNR) sscop->timernr = p->timer_no_response; if (mask & SSCOP_SET_TIDLE) sscop->timeridle = p->timer_idle; if (mask & SSCOP_SET_MAXK) sscop->maxk = p->maxk; if (mask & SSCOP_SET_MAXJ) sscop->maxj = p->maxj; if (mask & SSCOP_SET_MAXCC) sscop->maxcc = p->maxcc; if (mask & SSCOP_SET_MAXPD) sscop->maxpd = p->maxpd; if (mask & SSCOP_SET_MAXSTAT) sscop->maxstat = p->maxstat; if (mask & SSCOP_SET_MR) sscop->mr = p->mr; if (mask & SSCOP_SET_ROBUST) sscop->robustness = ((p->flags & SSCOP_ROBUST) != 0); if (mask & SSCOP_SET_POLLREX) sscop->poll_after_rex = ((p->flags & SSCOP_POLLREX) != 0); return (0); } enum sscop_state sscop_getstate(const struct sscop *sscop) { return (sscop->state); } /************************************************************/ /* * EXTERNAL INPUT SIGNAL MAPPING */ /* * Map AA signal to SSCOP internal signal */ int sscop_aasig(struct sscop *sscop, enum sscop_aasig sig, struct SSCOP_MBUF_T *m, u_int arg) { struct sscop_msg *msg; if (sig >= sizeof(sscop_sigs)/sizeof(sscop_sigs[0])) { VERBOSE(sscop, SSCOP_DBG_INSIG, (sscop, sscop->aarg, "AA-Signal %u - bad signal", sig)); MBUF_FREE(m); return (EINVAL); } VERBOSE(sscop, SSCOP_DBG_INSIG, (sscop, sscop->aarg, "AA-Signal %s in state %s with%s message", sscop_sigs[sig], states[sscop->state], m ? "" : "out")); MSG_ALLOC(msg); if (msg == NULL) { FAILURE("sscop: cannot allocate aasig"); MBUF_FREE(m); return (ENOMEM); } switch(sig) { case SSCOP_ESTABLISH_request: msg->m = m; msg->rexmit = arg; sscop_signal(sscop, SIG_ESTAB_REQ, msg); break; case SSCOP_ESTABLISH_response: msg->m = m; msg->rexmit = arg; sscop_signal(sscop, SIG_ESTAB_RESP, msg); break; case SSCOP_RELEASE_request: msg->m = m; sscop_signal(sscop, SIG_RELEASE_REQ, msg); break; case SSCOP_DATA_request: msg->m = m; sscop_signal(sscop, SIG_USER_DATA, msg); break; case SSCOP_UDATA_request: msg->m = m; sscop_signal(sscop, SIG_UDATA, msg); break; case SSCOP_RECOVER_response: MBUF_FREE(m); MSG_FREE(msg); sscop_signal(sscop, SIG_RECOVER, NULL); break; case SSCOP_RESYNC_request: msg->m = m; sscop_signal(sscop, SIG_SYNC_REQ, msg); break; case SSCOP_RESYNC_response: MBUF_FREE(m); MSG_FREE(msg); sscop_signal(sscop, SIG_SYNC_RESP, NULL); break; case SSCOP_RETRIEVE_request: MBUF_FREE(m); msg->rexmit = arg; sscop_signal(sscop, SIG_RETRIEVE, msg); break; case SSCOP_ESTABLISH_indication: case SSCOP_ESTABLISH_confirm: case SSCOP_RELEASE_indication: case SSCOP_RELEASE_confirm: case SSCOP_DATA_indication: case SSCOP_UDATA_indication: case SSCOP_RECOVER_indication: case SSCOP_RESYNC_indication: case SSCOP_RESYNC_confirm: case SSCOP_RETRIEVE_indication: case SSCOP_RETRIEVE_COMPL_indication: MBUF_FREE(m); MSG_FREE(msg); return EINVAL; } return 0; } /* * Signal from layer management. */ int sscop_maasig(struct sscop *sscop, enum sscop_maasig sig, struct SSCOP_MBUF_T *m) { struct sscop_msg *msg; if (sig >= sizeof(sscop_msigs)/sizeof(sscop_msigs[0])) { VERBOSE(sscop, SSCOP_DBG_INSIG, (sscop, sscop->aarg, "MAA-Signal %u - bad signal", sig)); MBUF_FREE(m); return (EINVAL); } VERBOSE(sscop, SSCOP_DBG_INSIG, (sscop, sscop->aarg, "MAA-Signal %s in state %s with%s message", sscop_msigs[sig], states[sscop->state], m ? "" : "out")); MSG_ALLOC(msg); if (msg == NULL) { FAILURE("sscop: cannot allocate maasig"); MBUF_FREE(m); return (ENOMEM); } switch (sig) { case SSCOP_MDATA_request: msg->m = m; sscop_signal(sscop, SIG_MDATA, msg); break; case SSCOP_MDATA_indication: case SSCOP_MERROR_indication: MBUF_FREE(m); MSG_FREE(msg); return (EINVAL); } return (0); } /* * Map PDU to SSCOP signal. */ void sscop_input(struct sscop *sscop, struct SSCOP_MBUF_T *m) { struct sscop_msg *msg; union pdu pdu; u_int size; MSG_ALLOC(msg); if(msg == NULL) { FAILURE("sscop: cannot allocate in pdu msg"); MBUF_FREE(m); return; } msg->m = m; msg->rexmit = 0; size = MBUF_LEN(m); if(size % 4 != 0 || size < 4) goto err; pdu.sscop_null = MBUF_TRAIL32(m, -1); VERBOSE(sscop, SSCOP_DBG_PDU, (sscop, sscop->aarg, "got %s, size=%u", pdus[pdu.sscop_type], size)); #ifdef SSCOP_DEBUG #define ENSURE(C,F) if(!(C)) { VERBOSE(sscop, SSCOP_DBG_PDU, F); goto err; } #else #define ENSURE(C,F) if(!(C)) goto err #endif #ifdef SSCOP_DEBUG if (ISVERBOSE(sscop, SSCOP_DBG_PDU)) sscop_dump_pdu(sscop, "rx", m); #endif switch(pdu.sscop_type) { default: ENSURE(0, (sscop, sscop->aarg, "Bad PDU type %u", pdu.sscop_type)); break; case PDU_BGN: ENSURE(size >= 8U, (sscop, sscop->aarg, "PDU_BGN size=%u", size)); ENSURE(size >= 8U + pdu.sscop_pl, (sscop, sscop->aarg, "PDU_BGN size=%u pl=%u", size, pdu.sscop_pl)); ENSURE(size <= 8U + sscop->maxj, (sscop, sscop->aarg, "PDU_BGN size=%u", size)); sscop_signal(sscop, SIG_BGN, msg); break; case PDU_BGAK: ENSURE(size >= 8U, (sscop, sscop->aarg, "PDU_BGAK size=%u", size)); ENSURE(size >= 8U + pdu.sscop_pl, (sscop, sscop->aarg, "PDU_BGAK size=%u pl=%u", size, pdu.sscop_pl)); ENSURE(size <= 8U + sscop->maxj, (sscop, sscop->aarg, "PDU_BGAK size=%u", size)); sscop_signal(sscop, SIG_BGAK, msg); break; case PDU_END: ENSURE(size >= 8U, (sscop, sscop->aarg, "PDU_END size=%u", size)); ENSURE(size >= 8U + pdu.sscop_pl, (sscop, sscop->aarg, "PDU_END size=%u pl=%u", size, pdu.sscop_pl)); ENSURE(size <= 8U + sscop->maxj, (sscop, sscop->aarg, "PDU_END size=%u", size)); sscop_signal(sscop, SIG_END, msg); break; case PDU_ENDAK: ENSURE(size == 8U, (sscop, sscop->aarg, "PDU_ENDAK size=%u", size)); sscop_signal(sscop, SIG_ENDAK, msg); break; case PDU_BGREJ: ENSURE(size >= 8U, (sscop, sscop->aarg, "PDU_BGREJ size=%u", size)); ENSURE(size >= 8U + pdu.sscop_pl, (sscop, sscop->aarg, "PDU_BGREJ size=%u pl=%u", size, pdu.sscop_pl)); ENSURE(size <= 8U + sscop->maxj, (sscop, sscop->aarg, "PDU_BGREJ size=%u", size)); sscop_signal(sscop, SIG_BGREJ, msg); break; case PDU_SD: ENSURE(size >= 4U + pdu.sscop_pl, (sscop, sscop->aarg, "PDU_SD size=%u pl=%u", size, pdu.sscop_pl)); ENSURE(size <= 4U + sscop->maxk, (sscop, sscop->aarg, "PDU_SD size=%u", size)); sscop_signal(sscop, SIG_SD, msg); break; case PDU_UD: ENSURE(size >= 4U + pdu.sscop_pl, (sscop, sscop->aarg, "PDU_UD size=%u pl=%u", size, pdu.sscop_pl)); ENSURE(size <= 4U + sscop->maxk, (sscop, sscop->aarg, "PDU_UD size=%u", size)); sscop_signal(sscop, SIG_UD, msg); break; case PDU_MD: ENSURE(size >= 4U + pdu.sscop_pl, (sscop, sscop->aarg, "PDU_MD size=%u pl=%u", size, pdu.sscop_pl)); ENSURE(size <= 4U + sscop->maxk, (sscop, sscop->aarg, "PDU_MD size=%u", size)); sscop_signal(sscop, SIG_MD, msg); break; case PDU_POLL: ENSURE(size == 8U, (sscop, sscop->aarg, "PDU_POLL size=%u", size)); sscop_signal(sscop, SIG_POLL, msg); break; case PDU_STAT: ENSURE(size >= 12U, (sscop, sscop->aarg, "PDU_STAT size=%u", size)); ENSURE(size <= 12U + 4 * sscop->maxstat, (sscop, sscop->aarg, "PDU_STAT size=%u", size)); sscop_signal(sscop, SIG_STAT, msg); break; case PDU_RS: ENSURE(size >= 8U, (sscop, sscop->aarg, "PDU_RS size=%u", size)); ENSURE(size >= 8U + pdu.sscop_pl, (sscop, sscop->aarg, "PDU_RS size=%u pl=%u", size, pdu.sscop_pl)); ENSURE(size <= 8U + sscop->maxj, (sscop, sscop->aarg, "PDU_RS size=%u", size)); sscop_signal(sscop, SIG_RS, msg); break; case PDU_RSAK: ENSURE(size == 8U, (sscop, sscop->aarg, "PDU_RSAK size=%u", size)); sscop_signal(sscop, SIG_RSAK, msg); break; case PDU_ER: ENSURE(size == 8U, (sscop, sscop->aarg, "PDU_ER size=%u", size)); sscop_signal(sscop, SIG_ER, msg); break; case PDU_ERAK: ENSURE(size == 8U, (sscop, sscop->aarg, "PDU_ERAK size=%u", size)); sscop_signal(sscop, SIG_ERAK, msg); break; case PDU_USTAT: ENSURE(size == 16U, (sscop, sscop->aarg, "PDU_ERAK size=%u", size)); sscop_signal(sscop, SIG_USTAT, msg); break; } #undef ENSURE return; err: MAAL_ERROR(sscop, 'U', 0); SSCOP_MSG_FREE(msg); } /************************************************************/ /* * UTILITIES */ /* * Move the receiver window by N packets */ u_int sscop_window(struct sscop *sscop, u_int n) { sscop->vr_mr += n; return (SEQNO_DIFF(sscop->vr_mr, sscop->vr_r)); } /* * Lower layer busy handling */ u_int sscop_setbusy(struct sscop *sscop, int busy) { u_int old = sscop->ll_busy; if (busy > 0) sscop->ll_busy = 1; else if (busy == 0) { sscop->ll_busy = 0; if(old) handle_sigs(sscop); } return (old); } const char * sscop_signame(enum sscop_aasig sig) { static char str[40]; if (sig >= sizeof(sscop_sigs)/sizeof(sscop_sigs[0])) { sprintf(str, "BAD SSCOP_AASIG %u", sig); return (str); } else { return (sscop_sigs[sig]); } } const char * sscop_msigname(enum sscop_maasig sig) { static char str[40]; if (sig >= sizeof(sscop_msigs)/sizeof(sscop_msigs[0])) { sprintf(str, "BAD SSCOP_MAASIG %u", sig); return (str); } else { return (sscop_msigs[sig]); } } const char * sscop_statename(enum sscop_state s) { static char str[40]; if (s >= sizeof(states)/sizeof(states[0])) { sprintf(str, "BAD SSCOP_STATE %u", s); return (str); } else { return (states[s]); } } /************************************************************/ /* * MACROS */ /* * p 75: release buffers */ static void m_release_buffers(struct sscop *sscop) { MSGQ_CLEAR(&sscop->xq); MSGQ_CLEAR(&sscop->xbuf); sscop->rxq = 0; MSGQ_CLEAR(&sscop->rbuf); } /* * P 75: Prepare retrival */ static void m_prepare_retrieval(struct sscop *sscop) { struct sscop_msg *msg; if (sscop->clear_buffers) { MSGQ_CLEAR(&sscop->xq); MSGQ_CLEAR(&sscop->xbuf); } MSGQ_FOREACH(msg, &sscop->xbuf) msg->rexmit = 0; sscop->rxq = 0; MSGQ_CLEAR(&sscop->rbuf); } /* * P 75: Prepare retrival */ static void m_prepare_recovery(struct sscop *sscop) { struct sscop_msg *msg; if(sscop->clear_buffers) { MSGQ_CLEAR(&sscop->xq); MSGQ_CLEAR(&sscop->xbuf); } MSGQ_FOREACH(msg, &sscop->xbuf) msg->rexmit = 0; sscop->rxq = 0; } /* * P 75: Clear transmitter */ static void m_clear_transmitter(struct sscop *sscop) { if(!sscop->clear_buffers) { MSGQ_CLEAR(&sscop->xq); MSGQ_CLEAR(&sscop->xbuf); } } /* * p 75: Deliver data * Freeing the message is the responibility of the handler function. */ static void m_deliver_data(struct sscop *sscop) { struct sscop_msg *msg; u_int sn; if ((msg = MSGQ_GET(&sscop->rbuf)) == NULL) return; if (sscop->clear_buffers) { MSGQ_CLEAR(&sscop->rbuf); return; } sn = msg->seqno + 1; AAL_DATA(sscop, SSCOP_DATA_indication, msg->m, msg->seqno); MSG_FREE(msg); while ((msg = MSGQ_GET(&sscop->rbuf)) != NULL) { ASSERT(msg->seqno == sn); if (++sn == SSCOP_MAXSEQNO) sn = 0; AAL_DATA(sscop, SSCOP_DATA_indication, msg->m, msg->seqno); MSG_FREE(msg); } } /* * P 75: Initialize state variables */ static void m_initialize_state(struct sscop *sscop) { sscop->vt_s = 0; sscop->vt_ps = 0; sscop->vt_a = 0; sscop->vt_pa = 1; sscop->vt_pd = 0; sscop->credit = 1; sscop->vr_r = 0; sscop->vr_h = 0; } /* * p 76: Data retrieval */ static void m_data_retrieval(struct sscop *sscop, u_int rn) { struct sscop_msg *s; if (rn != SSCOP_RETRIEVE_UNKNOWN) { if(rn >= SSCOP_RETRIEVE_TOTAL) rn = sscop->vt_a; else rn++; while(rn >= sscop->vt_a && rn < sscop->vt_s) { if(rn == SSCOP_MAXSEQNO) rn = 0; if((s = QFIND(&sscop->xbuf, rn)) != NULL) { MSGQ_REMOVE(&sscop->xbuf, s); AAL_DATA(sscop, SSCOP_RETRIEVE_indication, s->m, 0); MSG_FREE(s); } rn++; } } while((s = MSGQ_GET(&sscop->xq)) != NULL) { AAL_DATA(sscop, SSCOP_RETRIEVE_indication, s->m, 0); MSG_FREE(s); } AAL_SIG(sscop, SSCOP_RETRIEVE_COMPL_indication); } /* * P 76: Detect retransmission. PDU type must already be stripped. */ static int m_detect_retransmission(struct sscop *sscop, struct sscop_msg *msg) { union bgn bgn; bgn.sscop_null = MBUF_TRAIL32(msg->m, -1); if (sscop->vr_sq == bgn.sscop_bgns) return (1); sscop->vr_sq = bgn.sscop_bgns; return (0); } /* * P 76: Set POLL timer */ static void m_set_poll_timer(struct sscop *sscop) { if(MSGQ_EMPTY(&sscop->xq) && sscop->vt_s == sscop->vt_a) TIMER_RESTART(sscop, ka); else TIMER_RESTART(sscop, poll); } /* * P 77: Reset data transfer timers */ static void m_reset_data_xfer_timers(struct sscop *sscop) { TIMER_STOP(sscop, ka); TIMER_STOP(sscop, nr); TIMER_STOP(sscop, idle); TIMER_STOP(sscop, poll); } /* * P 77: Set data transfer timers */ static void m_set_data_xfer_timers(struct sscop *sscop) { TIMER_RESTART(sscop, poll); TIMER_RESTART(sscop, nr); } /* * P 77: Initialize VR(MR) */ static void m_initialize_mr(struct sscop *sscop) { sscop->vr_mr = sscop->mr; } /************************************************************/ /* * CONDITIONS */ static int c_ready_pduq(struct sscop *sscop) { if (!sscop->ll_busy && (sscop->rxq != 0 || sscop->vt_s < sscop->vt_ms || TIMER_ISACT(sscop, idle))) return (1); return (0); } /************************************************************/ /* * SEND PDUS */ /* * Send BG PDU. */ static void send_bgn(struct sscop *sscop, struct SSCOP_MBUF_T *uu) { union pdu pdu; union bgn bgn; struct SSCOP_MBUF_T *m; pdu.sscop_null = 0; pdu.sscop_type = PDU_BGN; pdu.sscop_ns = sscop->vr_mr; bgn.sscop_null = 0; bgn.sscop_bgns = sscop->vt_sq; if(uu) { if ((m = MBUF_DUP(uu)) == NULL) { FAILURE("sscop: cannot allocate BGN"); return; } pdu.sscop_pl += MBUF_PAD4(m); } else { if ((m = MBUF_ALLOC(8)) == NULL) { FAILURE("sscop: cannot allocate BGN"); return; } } MBUF_APPEND32(m, bgn.sscop_null); MBUF_APPEND32(m, pdu.sscop_null); AAL_SEND(sscop, m); } /* * Send BGREJ PDU. */ static void send_bgrej(struct sscop *sscop, struct SSCOP_MBUF_T *uu) { union pdu pdu; union bgn bgn; struct SSCOP_MBUF_T *m; pdu.sscop_null = 0; pdu.sscop_type = PDU_BGREJ; bgn.sscop_null = 0; if(uu) { if((m = MBUF_DUP(uu)) == NULL) { FAILURE("sscop: cannot allocate BGREJ"); return; } pdu.sscop_pl += MBUF_PAD4(m); } else { if((m = MBUF_ALLOC(8)) == NULL) { FAILURE("sscop: cannot allocate BGREJ"); return; } } MBUF_APPEND32(m, bgn.sscop_null); MBUF_APPEND32(m, pdu.sscop_null); AAL_SEND(sscop, m); } /* * Send BGAK PDU. */ static void send_bgak(struct sscop *sscop, struct SSCOP_MBUF_T *uu) { union pdu pdu; union bgn bgn; struct SSCOP_MBUF_T *m; pdu.sscop_null = 0; pdu.sscop_type = PDU_BGAK; pdu.sscop_ns = sscop->vr_mr; bgn.sscop_null = 0; if(uu) { if((m = MBUF_DUP(uu)) == NULL) { FAILURE("sscop: cannot allocate BGAK"); return; } pdu.sscop_pl += MBUF_PAD4(m); } else { if((m = MBUF_ALLOC(8)) == NULL) { FAILURE("sscop: cannot allocate BGAK"); return; } } MBUF_APPEND32(m, bgn.sscop_null); MBUF_APPEND32(m, pdu.sscop_null); AAL_SEND(sscop, m); } /* * Send SD PDU. The function makes a duplicate of the message. */ static void send_sd(struct sscop *sscop, struct SSCOP_MBUF_T *m, u_int seqno) { union pdu pdu; if((m = MBUF_DUP(m)) == NULL) { FAILURE("sscop: cannot allocate SD"); return; } pdu.sscop_null = 0; pdu.sscop_pl = 0; pdu.sscop_type = PDU_SD; pdu.sscop_ns = seqno; pdu.sscop_pl += MBUF_PAD4(m); MBUF_APPEND32(m, pdu.sscop_null); AAL_SEND(sscop, m); } /* * Send a UD PDU. The caller must free the sscop msg part. */ static void send_ud(struct sscop *sscop, struct SSCOP_MBUF_T *m) { union pdu pdu; pdu.sscop_null = 0; pdu.sscop_type = PDU_UD; pdu.sscop_pl += MBUF_PAD4(m); MBUF_APPEND32(m, pdu.sscop_null); AAL_SEND(sscop, m); } /* * Send a MD PDU. The caller must free the sscop msg part. */ static void send_md(struct sscop *sscop, struct SSCOP_MBUF_T *m) { union pdu pdu; pdu.sscop_null = 0; pdu.sscop_type = PDU_MD; pdu.sscop_pl += MBUF_PAD4(m); MBUF_APPEND32(m, pdu.sscop_null); AAL_SEND(sscop, m); } /* * Send END PDU. */ static void send_end(struct sscop *sscop, int src, struct SSCOP_MBUF_T *uu) { union pdu pdu; struct SSCOP_MBUF_T *m; sscop->last_end_src = src; pdu.sscop_null = 0; pdu.sscop_s = src; pdu.sscop_type = PDU_END; if(uu) { if((m = MBUF_DUP(uu)) == NULL) { FAILURE("sscop: cannot allocate END"); return; } pdu.sscop_pl += MBUF_PAD4(m); } else { if((m = MBUF_ALLOC(8)) == NULL) { FAILURE("sscop: cannot allocate END"); return; } } MBUF_APPEND32(m, 0); MBUF_APPEND32(m, pdu.sscop_null); AAL_SEND(sscop, m); } /* * Send USTAT PDU. List must be terminated by -1. */ static void send_ustat(struct sscop *sscop, ...) { va_list ap; int f; u_int n; union pdu pdu; union seqno seqno; struct SSCOP_MBUF_T *m; va_start(ap, sscop); n = 0; while((f = va_arg(ap, int)) >= 0) n++; va_end(ap); if((m = MBUF_ALLOC(n * 4 + 8)) == NULL) { FAILURE("sscop: cannot allocate USTAT"); return; } va_start(ap, sscop); while((f = va_arg(ap, int)) >= 0) { seqno.sscop_null = 0; seqno.sscop_n = f; MBUF_APPEND32(m, seqno.sscop_null); } va_end(ap); seqno.sscop_null = 0; seqno.sscop_n = sscop->vr_mr; MBUF_APPEND32(m, seqno.sscop_null); pdu.sscop_null = 0; pdu.sscop_type = PDU_USTAT; pdu.sscop_ns = sscop->vr_r; MBUF_APPEND32(m, pdu.sscop_null); AAL_SEND(sscop, m); } /* * Send ER PDU. */ static void send_er(struct sscop *sscop) { union pdu pdu; union bgn bgn; struct SSCOP_MBUF_T *m; pdu.sscop_null = 0; pdu.sscop_type = PDU_ER; pdu.sscop_ns = sscop->vr_mr; bgn.sscop_null = 0; bgn.sscop_bgns = sscop->vt_sq; if((m = MBUF_ALLOC(8)) == NULL) { FAILURE("sscop: cannot allocate ER"); return; } MBUF_APPEND32(m, bgn.sscop_null); MBUF_APPEND32(m, pdu.sscop_null); AAL_SEND(sscop, m); } /* * Send POLL PDU. */ static void send_poll(struct sscop *sscop) { union pdu pdu; union seqno seqno; struct SSCOP_MBUF_T *m; seqno.sscop_null = 0; seqno.sscop_n = sscop->vt_ps; pdu.sscop_null = 0; pdu.sscop_ns = sscop->vt_s; pdu.sscop_type = PDU_POLL; if((m = MBUF_ALLOC(8)) == NULL) { FAILURE("sscop: cannot allocate POLL"); return; } MBUF_APPEND32(m, seqno.sscop_null); MBUF_APPEND32(m, pdu.sscop_null); AAL_SEND(sscop, m); } /* * Send STAT PDU. List is already in buffer. */ static void send_stat(struct sscop *sscop, u_int nps, struct SSCOP_MBUF_T *m) { union pdu pdu; union seqno seqno; seqno.sscop_null = 0; seqno.sscop_n = nps; MBUF_APPEND32(m, seqno.sscop_null); seqno.sscop_null = 0; seqno.sscop_n = sscop->vr_mr; MBUF_APPEND32(m, seqno.sscop_null); pdu.sscop_null = 0; pdu.sscop_type = PDU_STAT; pdu.sscop_ns = sscop->vr_r; MBUF_APPEND32(m, pdu.sscop_null); AAL_SEND(sscop, m); } /* * Send ENDAK PDU. */ static void send_endak(struct sscop *sscop) { union pdu pdu; union seqno seqno; struct SSCOP_MBUF_T *m; seqno.sscop_null = 0; pdu.sscop_null = 0; pdu.sscop_type = PDU_ENDAK; if((m = MBUF_ALLOC(8)) == NULL) { FAILURE("sscop: cannot allocate ENDAK"); return; } MBUF_APPEND32(m, seqno.sscop_null); MBUF_APPEND32(m, pdu.sscop_null); AAL_SEND(sscop, m); } /* * Send ERAK PDU. */ static void send_erak(struct sscop *sscop) { union pdu pdu; union seqno seqno; struct SSCOP_MBUF_T *m; seqno.sscop_null = 0; pdu.sscop_null = 0; pdu.sscop_type = PDU_ERAK; pdu.sscop_ns = sscop->vr_mr; if((m = MBUF_ALLOC(8)) == NULL) { FAILURE("sscop: cannot allocate ERAK"); return; } MBUF_APPEND32(m, seqno.sscop_null); MBUF_APPEND32(m, pdu.sscop_null); AAL_SEND(sscop, m); } /* * Send RS PDU */ static void send_rs(struct sscop *sscop, int resend, struct SSCOP_MBUF_T *uu) { union pdu pdu; union bgn bgn; struct SSCOP_MBUF_T *m; pdu.sscop_null = 0; pdu.sscop_type = PDU_RS; pdu.sscop_ns = resend ? sscop->rs_mr : sscop->vr_mr; bgn.sscop_null = 0; bgn.sscop_bgns = resend ? sscop->rs_sq : sscop->vt_sq; sscop->rs_mr = pdu.sscop_ns; sscop->rs_sq = bgn.sscop_bgns; if(uu) { if((m = MBUF_DUP(uu)) == NULL) { FAILURE("sscop: cannot allocate RS"); return; } pdu.sscop_pl += MBUF_PAD4(m); } else { if((m = MBUF_ALLOC(8)) == NULL) { FAILURE("sscop: cannot allocate RS"); return; } } MBUF_APPEND32(m, bgn.sscop_null); MBUF_APPEND32(m, pdu.sscop_null); AAL_SEND(sscop, m); } /* * Send RSAK pdu */ static void send_rsak(struct sscop *sscop) { union pdu pdu; union seqno seqno; struct SSCOP_MBUF_T *m; seqno.sscop_null = 0; pdu.sscop_null = 0; pdu.sscop_type = PDU_RSAK; pdu.sscop_ns = sscop->vr_mr; if((m = MBUF_ALLOC(8)) == NULL) { FAILURE("sscop: cannot allocate RSAK"); return; } MBUF_APPEND32(m, seqno.sscop_null); MBUF_APPEND32(m, pdu.sscop_null); AAL_SEND(sscop, m); } /************************************************************/ /* * P 31; IDLE && AA-ESTABLISH-request * arg is UU data (opt). */ static void sscop_idle_establish_req(struct sscop *sscop, struct sscop_msg *uu) { u_int br = uu->rexmit; SET_UU(uu_bgn, uu); m_clear_transmitter(sscop); sscop->clear_buffers = br; sscop->vt_cc = 1; sscop->vt_sq++; m_initialize_mr(sscop); send_bgn(sscop, sscop->uu_bgn); TIMER_RESTART(sscop, cc); sscop_set_state(sscop, SSCOP_OUT_PEND); } /* * P 31: IDLE && BGN PDU * arg is the received PDU (freed). */ static void sscop_idle_bgn(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; union bgn bgn; pdu.sscop_null = MBUF_STRIP32(msg->m); if(sscop->robustness) { bgn.sscop_null = MBUF_STRIP32(msg->m); sscop->vr_sq = bgn.sscop_bgns; } else { if(m_detect_retransmission(sscop, msg)) { send_bgrej(sscop, sscop->uu_bgrej); SSCOP_MSG_FREE(msg); return; } (void)MBUF_STRIP32(msg->m); } sscop->vt_ms = pdu.sscop_ns; sscop_set_state(sscop, SSCOP_IN_PEND); AAL_UU_SIGNAL(sscop, SSCOP_ESTABLISH_indication, msg, pdu.sscop_pl, 0); } /* * p 31: IDLE && ENDAK PDU * p 34: OUT_PEND && ENDAK PDU * p 34: OUT_PEND && SD PDU * p 34: OUT_PEND && ERAK PDU * p 34: OUT_PEND && END PDU * p 34: OUT_PEND && STAT PDU * p 34: OUT_PEND && USTAT PDU * p 34: OUT_PEND && POLL PDU * p 36: OUT_PEND && RS PDU * p 36: OUT_PEND && RSAK PDU * p 40: OUTGOING_DISCONNECT_PENDING && SD PDU * p 40: OUTGOING_DISCONNECT_PENDING && BGAK PDU * p 40: OUTGOING_DISCONNECT_PENDING && POLL PDU * p 40: OUTGOING_DISCONNECT_PENDING && STAT PDU * p 40: OUTGOING_DISCONNECT_PENDING && USTAT PDU * p 41: OUTGOING_DISCONNECT_PENDING && ERAK PDU * p 42: OUTGOING_DISCONNECT_PENDING && ER PDU * p 42: OUTGOING_DISCONNECT_PENDING && RS PDU * p 42: OUTGOING_DISCONNECT_PENDING && RSAK PDU * p 43: OUTGOING_RESYNC && ER PDU * p 43: OUTGOING_RESYNC && POLL PDU * p 44: OUTGOING_RESYNC && STAT PDU * p 44: OUTGOING_RESYNC && USTAT PDU * p 45: OUTGOING_RESYNC && BGAK PDU * p 45: OUTGOING_RESYNC && SD PDU * p 45: OUTGOING_RESYNC && ERAK PDU * P 60: READY && BGAK PDU * P 60: READY && ERAK PDU * arg is pdu (freed). */ static void sscop_ignore_pdu(struct sscop *sscop __unused, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); } /* * p 31: IDLE && END PDU * arg is pdu (freed). */ static void sscop_idle_end(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); send_endak(sscop); } /* * p 31: IDLE && ER PDU * arg is pdu (freed). */ static void sscop_idle_er(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'L', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); } /* * p 31: IDLE && BGREJ PDU * arg is pdu (freed). */ static void sscop_idle_bgrej(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'D', 0); FREE_UU(uu_end); } /* * p 32: IDLE && POLL PDU * arg is pdu (freed). */ static void sscop_idle_poll(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'G', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); } /* * p 32: IDLE && SD PDU * arg is pdu (freed). */ static void sscop_idle_sd(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'A', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); } /* * p 32: IDLE && BGAK PDU * arg is pdu (freed). */ static void sscop_idle_bgak(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'C', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); } /* * p 32: IDLE && ERAK PDU * arg is pdu (freed). */ static void sscop_idle_erak(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'M', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); } /* * p 32: IDLE && STAT PDU * arg is pdu (freed). */ static void sscop_idle_stat(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'H', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); } /* * p 32: IDLE && USTAT PDU * arg is pdu (freed). */ static void sscop_idle_ustat(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'I', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); } /* * p 33: IDLE & RS PDU * arg is pdu (freed). */ static void sscop_idle_rs(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'J', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); } /* * p 33: IDLE & RSAK PDU * arg is pdu (freed). */ static void sscop_idle_rsak(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'K', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); } /* * p 33: IDLE && PDU_Q * p XX: OUTPEND && PDU_Q * p 39: IN_PEND && PDU_Q * p 45: OUT_RESYNC_PEND && PDU_Q * p 48: IN_RESYNC_PEND && PDU_Q * no arg */ static void sscop_flush_pduq(struct sscop *sscop __unused, struct sscop_msg *unused __unused) { #if 0 MSGQ_CLEAR(&sscop->xq); #endif } /* * p 34: OUT_PEND && BGAK PDU * arg is pdu (freed). */ static void sscop_outpend_bgak(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); (void)MBUF_STRIP32(msg->m); TIMER_STOP(sscop, cc); sscop->vt_ms = pdu.sscop_ns; AAL_UU_SIGNAL(sscop, SSCOP_ESTABLISH_confirm, msg, pdu.sscop_pl, 0); m_initialize_state(sscop); m_set_data_xfer_timers(sscop); sscop_set_state(sscop, SSCOP_READY); } /* * P 34: OUT_PEND && BGREJ PDU */ static void sscop_outpend_bgrej(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); (void)MBUF_STRIP32(msg->m); TIMER_STOP(sscop, cc); AAL_UU_SIGNAL(sscop, SSCOP_RELEASE_indication, msg, pdu.sscop_pl, 0); sscop_set_state(sscop, SSCOP_IDLE); } /* * P 35: OUT_PEND && TIMER_CC expiry * no arg */ static void sscop_outpend_tcc(struct sscop *sscop, struct sscop_msg *unused __unused) { if(sscop->vt_cc >= sscop->maxcc) { MAAL_ERROR(sscop, 'O', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); } else { sscop->vt_cc++; send_bgn(sscop, sscop->uu_bgn); TIMER_RESTART(sscop, cc); } } /* * P 35: OUT_PEND && RELEASE_REQ * arg is UU */ static void sscop_outpend_release_req(struct sscop *sscop, struct sscop_msg *uu) { SET_UU(uu_end, uu); TIMER_STOP(sscop, cc); sscop->vt_cc = 1; send_end(sscop, 0, sscop->uu_end); TIMER_RESTART(sscop, cc); sscop_set_state(sscop, SSCOP_OUT_DIS_PEND); } /* * P 36: OUT_PEND && BGN PDU * arg is the received PDU (freed). */ static void sscop_outpend_bgn(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { SSCOP_MSG_FREE(msg); return; } (void)MBUF_STRIP32(msg->m); TIMER_STOP(sscop, cc); sscop->vt_ms = pdu.sscop_ns; m_initialize_mr(sscop); send_bgak(sscop, sscop->uu_bgak); AAL_UU_SIGNAL(sscop, SSCOP_ESTABLISH_confirm, msg, pdu.sscop_pl, 0); m_initialize_state(sscop); m_set_data_xfer_timers(sscop); sscop_set_state(sscop, SSCOP_READY); } /* * p 37: IN_PEND && AA-ESTABLISH.response * arg is UU */ static void sscop_inpend_establish_resp(struct sscop *sscop, struct sscop_msg *uu) { u_int br = uu->rexmit; SET_UU(uu_bgak, uu); m_clear_transmitter(sscop); sscop->clear_buffers = br; m_initialize_mr(sscop); send_bgak(sscop, sscop->uu_bgak); m_initialize_state(sscop); m_set_data_xfer_timers(sscop); sscop_set_state(sscop, SSCOP_READY); } /* * p 37: IN_PEND && AA-RELEASE.request * arg is uu. */ static void sscop_inpend_release_req(struct sscop *sscop, struct sscop_msg *uu) { SET_UU(uu_bgrej, uu); send_bgrej(sscop, sscop->uu_bgrej); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 37: IN_PEND && BGN PDU * arg is pdu. (freed) */ static void sscop_inpend_bgn(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { SSCOP_MSG_FREE(msg); return; } (void)MBUF_STRIP32(msg->m); sscop->vt_ms = pdu.sscop_ns; AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 0); AAL_UU_SIGNAL(sscop, SSCOP_ESTABLISH_indication, msg, pdu.sscop_pl, 0); } /* * p 37: IN_PEND && ER PDU * arg is pdu (freed). */ static void sscop_inpend_er(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'L', 0); SSCOP_MSG_FREE(msg); } /* * p 37: IN_PEND && ENDAK PDU * arg is pdu (freed). */ static void sscop_inpend_endak(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'F', 0); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); SSCOP_MSG_FREE(msg); } /* * p 38: IN_PEND && BGAK PDU * arg is pdu (freed). */ static void sscop_inpend_bgak(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'C', 0); SSCOP_MSG_FREE(msg); } /* * p 38: IN_PEND && BGREJ PDU * arg is pdu (freed). */ static void sscop_inpend_bgrej(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'D', 0); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); SSCOP_MSG_FREE(msg); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 38: IN_PEND && SD PDU * arg is pdu (freed). */ static void sscop_inpend_sd(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'A', 0); SSCOP_MSG_FREE(msg); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 38: IN_PEND && USTAT PDU * arg is pdu (freed). */ static void sscop_inpend_ustat(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'I', 0); SSCOP_MSG_FREE(msg); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 38: IN_PEND && STAT PDU * arg is pdu (freed). */ static void sscop_inpend_stat(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'H', 0); SSCOP_MSG_FREE(msg); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 38: IN_PEND && POLL PDU * arg is pdu (freed). */ static void sscop_inpend_poll(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'G', 0); SSCOP_MSG_FREE(msg); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 39: IN_PEND && ERAK PDU * arg is pdu (freed). */ static void sscop_inpend_erak(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'M', 0); } /* * p 39: IN_PEND & RS PDU * arg is pdu (freed). */ static void sscop_inpend_rs(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'J', 0); } /* * p 39: IN_PEND & RSAK PDU * arg is pdu (freed). */ static void sscop_inpend_rsak(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'K', 0); } /* * p 39: IN_PEND && END PDU * arg is pdu (freed). * no uui */ static void sscop_inpend_end(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); (void)MBUF_STRIP32(msg->m); send_endak(sscop); AAL_UU_SIGNAL(sscop, SSCOP_RELEASE_indication, msg, pdu.sscop_pl, (u_int)pdu.sscop_s); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 40: OUT_DIS_PEND && SSCOP_ESTABLISH_request * no arg. * no uui. */ static void sscop_outdis_establish_req(struct sscop *sscop, struct sscop_msg *uu) { SET_UU(uu_bgn, uu); TIMER_STOP(sscop, cc); m_clear_transmitter(sscop); sscop->clear_buffers = 1; sscop->vt_cc = 1; sscop->vt_sq++; m_initialize_mr(sscop); send_bgn(sscop, sscop->uu_bgn); TIMER_RESTART(sscop, cc); sscop_set_state(sscop, SSCOP_OUT_PEND); } /* * p 41: OUT_DIS_PEND && END PDU * arg is pdu (freed). */ static void sscop_outdis_end(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); (void)MBUF_STRIP32(msg->m); TIMER_STOP(sscop, cc); send_endak(sscop); AAL_UU_SIGNAL(sscop, SSCOP_RELEASE_confirm, msg, pdu.sscop_pl, 0); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 41: OUT_DIS_PEND && ENDAK PDU * p 41: OUT_DIS_PEND && BGREJ PDU * arg is pdu (freed) */ static void sscop_outdis_endak(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); (void)MBUF_STRIP32(msg->m); TIMER_STOP(sscop, cc); AAL_UU_SIGNAL(sscop, SSCOP_RELEASE_confirm, msg, pdu.sscop_pl, 0); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 41: OUT_DIS_PEND && TIMER CC expiry * no arg */ static void sscop_outdis_cc(struct sscop *sscop, struct sscop_msg *unused __unused) { if(sscop->vt_cc >= sscop->maxcc) { MAAL_ERROR(sscop, 'O', 0); AAL_SIG(sscop, SSCOP_RELEASE_confirm); sscop_set_state(sscop, SSCOP_IDLE); } else { sscop->vt_cc++; send_end(sscop, sscop->last_end_src, sscop->uu_end); TIMER_RESTART(sscop, cc); } } /* * p 42: OUT_DIS_PEND && BGN PDU * arg is pdu (freed). */ static void sscop_outdis_bgn(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { FREE_UU(uu_bgak); send_bgak(sscop, NULL); send_end(sscop, sscop->last_end_src, sscop->uu_end); SSCOP_MSG_FREE(msg); } else { (void)MBUF_STRIP32(msg->m); TIMER_STOP(sscop, cc); sscop->vt_ms = pdu.sscop_ns; AAL_SIG(sscop, SSCOP_RELEASE_confirm); AAL_UU_SIGNAL(sscop, SSCOP_ESTABLISH_indication, msg, pdu.sscop_pl, 0); sscop_set_state(sscop, SSCOP_IN_PEND); } } /* * p 43: OUT_RESYNC_PEND && BGN PDU * arg is pdu (freed). */ static void sscop_outsync_bgn(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { send_bgak(sscop, sscop->uu_bgak); send_rs(sscop, 1, sscop->uu_rs); SSCOP_MSG_FREE(msg); } else { (void)MBUF_STRIP32(msg->m); TIMER_STOP(sscop, cc); sscop->vt_ms = pdu.sscop_ns; AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 0); AAL_UU_SIGNAL(sscop, SSCOP_ESTABLISH_indication, msg, pdu.sscop_pl, 0); sscop_set_state(sscop, SSCOP_IN_PEND); } } /* * p 43: OUT_RESYNC_PEND && ENDAK PDU * arg is pdu (freed). */ static void sscop_outsync_endak(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); TIMER_STOP(sscop, cc); MAAL_ERROR(sscop, 'F', 0); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 43: OUT_RESYNC_PEND && BGREJ PDU * arg is pdu (freed). */ static void sscop_outsync_bgrej(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); TIMER_STOP(sscop, cc); MAAL_ERROR(sscop, 'D', 0); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 43: OUT_RESYNC_PEND && END PDU * arg is pdu (freed). * no UU-data */ static void sscop_outsync_end(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); (void)MBUF_STRIP32(msg->m); TIMER_STOP(sscop, cc); send_endak(sscop); AAL_UU_SIGNAL(sscop, SSCOP_RELEASE_indication, msg, pdu.sscop_pl, (u_int)pdu.sscop_s); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 44: OUT_RESYNC && TIMER CC expiry */ static void sscop_outsync_cc(struct sscop *sscop, struct sscop_msg *msg __unused) { if(sscop->vt_cc == sscop->maxcc) { MAAL_ERROR(sscop, 'O', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); } else { sscop->vt_cc++; send_rs(sscop, 1, sscop->uu_rs); TIMER_RESTART(sscop, cc); } } /* * p 44: OUT_RESYNC && AA-RELEASE.request * arg is UU */ static void sscop_outsync_release_req(struct sscop *sscop, struct sscop_msg *uu) { SET_UU(uu_end, uu); TIMER_STOP(sscop, cc); sscop->vt_cc = 1; send_end(sscop, 0, sscop->uu_end); TIMER_RESTART(sscop, cc); sscop_set_state(sscop, SSCOP_OUT_DIS_PEND); } /* * p 45: OUT_RESYNC && RS PDU * arg is pdu (freed). */ static void sscop_outsync_rs(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { SSCOP_MSG_FREE(msg); return; } (void)MBUF_STRIP32(msg->m); TIMER_STOP(sscop, cc); sscop->vt_ms = pdu.sscop_ns; m_initialize_mr(sscop); send_rsak(sscop); AAL_UU_SIGNAL(sscop, SSCOP_RESYNC_confirm, msg, pdu.sscop_pl, 0); m_initialize_state(sscop); m_set_data_xfer_timers(sscop); sscop_set_state(sscop, SSCOP_READY); } /* * p 45: OUT_RESYNC && RSAK PDU * arg is pdu (freed). */ static void sscop_outsync_rsak(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); SSCOP_MSG_FREE(msg); TIMER_STOP(sscop, cc); sscop->vt_ms = pdu.sscop_ns; AAL_SIG(sscop, SSCOP_RESYNC_confirm); m_initialize_state(sscop); m_set_data_xfer_timers(sscop); sscop_set_state(sscop, SSCOP_READY); } /* * p 46: IN_RESYNC_PEND && AA-RESYNC.response */ static void sscop_insync_sync_resp(struct sscop *sscop, struct sscop_msg *noarg __unused) { m_initialize_mr(sscop); send_rsak(sscop); m_clear_transmitter(sscop); m_initialize_state(sscop); m_set_data_xfer_timers(sscop); sscop_set_state(sscop, SSCOP_READY); } /* * p 46: IN_RESYNC_PEND && AA-RELEASE.request * arg is uu */ static void sscop_insync_release_req(struct sscop *sscop, struct sscop_msg *uu) { SET_UU(uu_end, uu); sscop->vt_cc = 1; send_end(sscop, 0, sscop->uu_end); TIMER_RESTART(sscop, cc); sscop_set_state(sscop, SSCOP_OUT_DIS_PEND); } /* * p 46: IN_RESYNC_PEND && ENDAK PDU * arg is pdu (freed). */ static void sscop_insync_endak(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'F', 0); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 46: IN_RESYNC_PEND && BGREJ PDU * arg is pdu (freed). */ static void sscop_insync_bgrej(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'D', 0); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 46: IN_RESYNC_PEND && END PDU * arg is pdu (freed). */ static void sscop_insync_end(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); (void)MBUF_STRIP32(msg->m); send_endak(sscop); AAL_UU_SIGNAL(sscop, SSCOP_RELEASE_indication, msg, pdu.sscop_pl, (u_int)pdu.sscop_s); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 47: IN_RESYNC_PEND && ER PDU * arg is pdu (freed). */ static void sscop_insync_er(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'L', 0); } /* * p 47: IN_RESYNC_PEND && BGN PDU * arg is pdu (freed). */ static void sscop_insync_bgn(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { MAAL_ERROR(sscop, 'B', 0); SSCOP_MSG_FREE(msg); return; } (void)MBUF_STRIP32(msg->m); sscop->vt_ms = pdu.sscop_ns; AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 0); AAL_UU_SIGNAL(sscop, SSCOP_ESTABLISH_indication, msg, pdu.sscop_pl, 0); sscop_set_state(sscop, SSCOP_IN_PEND); } /* * p 47: IN_RESYNC_PEND && SD PDU * arg is pdu (freed). */ static void sscop_insync_sd(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'A', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 47: IN_RESYNC_PEND && POLL PDU * arg is pdu (freed). */ static void sscop_insync_poll(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'G', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 47: IN_RESYNC_PEND && STAT PDU * arg is pdu (freed). */ static void sscop_insync_stat(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'H', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 47: IN_RESYNC_PEND && USTAT PDU * arg is pdu (freed). */ static void sscop_insync_ustat(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'I', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 48: IN_RESYNC_PEND && BGAK PDU * arg is pdu (freed). */ static void sscop_insync_bgak(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'C', 0); SSCOP_MSG_FREE(msg); } /* * p 48: IN_RESYNC_PEND && ERAK PDU * arg is pdu (freed). */ static void sscop_insync_erak(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'M', 0); SSCOP_MSG_FREE(msg); } /* * p 48: IN_RESYNC_PEND && RS PDU * arg is pdu (freed). */ static void sscop_insync_rs(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { SSCOP_MSG_FREE(msg); return; } SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'J', 0); } /* * p 48: IN_RESYNC_PEND && RSAK PDU * arg is pdu (freed). */ static void sscop_insync_rsak(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'K', 0); SSCOP_MSG_FREE(msg); } /* * p 49: OUT_REC_PEND && AA-DATA.request * arg is message (queued). */ static void sscop_outrec_userdata(struct sscop *sscop, struct sscop_msg *msg) { if(!sscop->clear_buffers) { MSGQ_APPEND(&sscop->xq, msg); sscop_signal(sscop, SIG_PDU_Q, msg); } else { SSCOP_MSG_FREE(msg); } } /* * p 49: OUT_REC_PEND && BGAK PDU * arg is pdu (freed) */ static void sscop_outrec_bgak(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'C', 0); SSCOP_MSG_FREE(msg); } /* * p 49: OUT_REC_PEND && ERAK PDU * arg is pdu (freed) */ static void sscop_outrec_erak(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); TIMER_STOP(sscop, cc); sscop->vt_ms = pdu.sscop_ns; m_deliver_data(sscop); AAL_SIG(sscop, SSCOP_RECOVER_indication); sscop_set_state(sscop, SSCOP_REC_PEND); SSCOP_MSG_FREE(msg); } /* * p 49: OUT_REC_PEND && END PDU * arg is pdu (freed) */ static void sscop_outrec_end(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); (void)MBUF_STRIP32(msg->m); TIMER_STOP(sscop, cc); send_endak(sscop); AAL_UU_SIGNAL(sscop, SSCOP_RELEASE_indication, msg, pdu.sscop_pl, (u_int)pdu.sscop_s); MSGQ_CLEAR(&sscop->rbuf); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 49: OUT_REC_PEND && ENDAK PDU * arg is pdu (freed) */ static void sscop_outrec_endak(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'F', 0); TIMER_STOP(sscop, cc); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); MSGQ_CLEAR(&sscop->rbuf); sscop_set_state(sscop, SSCOP_IDLE); SSCOP_MSG_FREE(msg); } /* * p 49: OUT_REC_PEND && BGREJ PDU * arg is pdu (freed) */ static void sscop_outrec_bgrej(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'D', 0); TIMER_STOP(sscop, cc); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); MSGQ_CLEAR(&sscop->rbuf); sscop_set_state(sscop, SSCOP_IDLE); SSCOP_MSG_FREE(msg); } /* * p 50: OUT_REC_PEND && TIMER CC expiry * no arg. */ static void sscop_outrec_cc(struct sscop *sscop, struct sscop_msg *unused __unused) { if(sscop->vt_cc >= sscop->maxcc) { MAAL_ERROR(sscop, 'O', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); MSGQ_CLEAR(&sscop->rbuf); sscop_set_state(sscop, SSCOP_IDLE); } else { sscop->vt_cc++; send_er(sscop); TIMER_RESTART(sscop, cc); } } /* * p 50: OUT_REC_PEND && SSCOP_RELEASE_request * arg is UU */ static void sscop_outrec_release_req(struct sscop *sscop, struct sscop_msg *uu) { SET_UU(uu_end, uu); TIMER_STOP(sscop, cc); sscop->vt_cc = 1; send_end(sscop, 0, sscop->uu_end); MSGQ_CLEAR(&sscop->rbuf); TIMER_RESTART(sscop, cc); sscop_set_state(sscop, SSCOP_OUT_DIS_PEND); } /* * p 51: OUT_REC_PEND && AA-RESYNC.request * arg is uu */ static void sscop_outrec_sync_req(struct sscop *sscop, struct sscop_msg *uu) { SET_UU(uu_rs, uu); TIMER_STOP(sscop, cc); sscop->vt_cc = 1; sscop->vt_sq++; m_initialize_mr(sscop); send_rs(sscop, 0, sscop->uu_rs); m_clear_transmitter(sscop); MSGQ_CLEAR(&sscop->rbuf); TIMER_RESTART(sscop, cc); } /* * p 51: OUT_REC_PEND && BGN PDU * arg is pdu (freed). * no uui */ static void sscop_outrec_bgn(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { MAAL_ERROR(sscop, 'B', 0); SSCOP_MSG_FREE(msg); } else { (void)MBUF_STRIP32(msg->m); TIMER_STOP(sscop, cc); sscop->vt_ms = pdu.sscop_ns; AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 0); AAL_UU_SIGNAL(sscop, SSCOP_ESTABLISH_indication, msg, pdu.sscop_pl, 0); MSGQ_CLEAR(&sscop->rbuf); sscop_set_state(sscop, SSCOP_IN_PEND); } } /* * p 51: OUT_REC_PEND && ER PDU * arg is pdu (freed). */ static void sscop_outrec_er(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { MAAL_ERROR(sscop, 'L', 0); } else { TIMER_STOP(sscop, cc); sscop->vt_ms = pdu.sscop_ns; m_initialize_mr(sscop); send_erak(sscop); m_deliver_data(sscop); AAL_SIG(sscop, SSCOP_RECOVER_indication); sscop_set_state(sscop, SSCOP_REC_PEND); } SSCOP_MSG_FREE(msg); } /* * p 52: OUT_REC_PEND && SD PDU queued * no arg. */ static void sscop_outrec_pduq(struct sscop *sscop, struct sscop_msg *msg) { sscop_save_signal(sscop, SIG_PDU_Q, msg); } /* * p 52: OUT_REC_PEND && RSAK PDU * arg is pdu (freed). */ static void sscop_outrec_rsak(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'K', 0); } /* * p 52: OUT_REC_PEND && RS PDU * arg is pdu (freed). */ static void sscop_outrec_rs(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'J', 0); return; } (void)MBUF_STRIP32(msg->m); TIMER_STOP(sscop, cc); sscop->vt_ms = pdu.sscop_ns; AAL_UU_SIGNAL(sscop, SSCOP_RESYNC_indication, msg, pdu.sscop_pl, 0); MSGQ_CLEAR(&sscop->rbuf); sscop_set_state(sscop, SSCOP_IN_RESYNC_PEND); } /* * p 53: REC_PEND && BGAK PDU * arg is pdu (freed) */ static void sscop_rec_bgak(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'C', 0); SSCOP_MSG_FREE(msg); } /* * p 53: REC_PEND && END PDU * arg is pdu (freed) * no uui */ static void sscop_rec_end(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); (void)MBUF_STRIP32(msg->m); send_endak(sscop); AAL_UU_SIGNAL(sscop, SSCOP_RELEASE_indication, msg, pdu.sscop_pl, (u_int)pdu.sscop_s); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 53: REC_PEND && ENDAK PDU * arg is pdu (freed) */ static void sscop_rec_endak(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'F', 0); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); SSCOP_MSG_FREE(msg); } /* * p 53: REC_PEND && BGREJ PDU * arg is pdu (freed) */ static void sscop_rec_bgrej(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'D', 0); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); SSCOP_MSG_FREE(msg); } /* * p 54: REC_PEND && RELEASE * arg is UU */ static void sscop_rec_release_req(struct sscop *sscop, struct sscop_msg *uu) { SET_UU(uu_end, uu); sscop->vt_cc = 1; send_end(sscop, 0, sscop->uu_end); TIMER_RESTART(sscop, cc); sscop_set_state(sscop, SSCOP_OUT_DIS_PEND); } /* * p 54: REC_PEND && RSAK PDU * arg is pdu (freed). */ static void sscop_rec_rsak(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'K', 0); SSCOP_MSG_FREE(msg); } /* * p 54: REC_PEND && RS PDU * arg is pdu (freed). */ static void sscop_rec_rs(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'J', 0); return; } (void)MBUF_STRIP32(msg->m); sscop->vt_ms = pdu.sscop_ns; AAL_UU_SIGNAL(sscop, SSCOP_RESYNC_indication, msg, pdu.sscop_pl, 0); sscop_set_state(sscop, SSCOP_IN_RESYNC_PEND); } /* * p 54: REC_PEND && RECOVER response * no arg */ static void sscop_rec_recover(struct sscop *sscop, struct sscop_msg *unused __unused) { if(!sscop->clear_buffers) { MSGQ_CLEAR(&sscop->xbuf); } m_initialize_state(sscop); m_set_data_xfer_timers(sscop); sscop_set_state(sscop, SSCOP_READY); } /* * p 54: REC_PEND && RESYNC request * arg is uu */ static void sscop_rec_sync_req(struct sscop *sscop, struct sscop_msg *uu) { SET_UU(uu_rs, uu); m_clear_transmitter(sscop); sscop->vt_cc = 1; sscop->vt_sq++; m_initialize_mr(sscop); send_rs(sscop, 0, sscop->uu_rs); TIMER_RESTART(sscop, cc); sscop_set_state(sscop, SSCOP_OUT_RESYNC_PEND); } /* * p 55: REC_PEND && SD PDU queued * no arg */ static void sscop_rec_pduq(struct sscop *sscop, struct sscop_msg *msg) { sscop_save_signal(sscop, SIG_PDU_Q, msg); } /* * p 55: REC_PEND && ER PDU * arg is pdu (freed). */ static void sscop_rec_er(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { send_erak(sscop); } else { MAAL_ERROR(sscop, 'L', 0); } SSCOP_MSG_FREE(msg); } /* * p 55: REC_PEND && BGN PDU * arg is pdu (freed) * no uui */ static void sscop_rec_bgn(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { MAAL_ERROR(sscop, 'B', 0); SSCOP_MSG_FREE(msg); return; } (void)MBUF_STRIP32(msg->m); sscop->vt_ms = pdu.sscop_ns; AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 0); AAL_UU_SIGNAL(sscop, SSCOP_ESTABLISH_indication, msg, pdu.sscop_pl, 0); sscop_set_state(sscop, SSCOP_IN_PEND); } /* * p 55: REC_PEND && STAT PDU * arg is pdu (freed) */ static void sscop_rec_stat(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'H', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); SSCOP_MSG_FREE(msg); } /* * p 55: REC_PEND && USTAT PDU * arg is pdu (freed) */ static void sscop_rec_ustat(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'I', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); sscop_set_state(sscop, SSCOP_IDLE); SSCOP_MSG_FREE(msg); } /* * p 56: IN_REC_PEND && AA-RECOVER.response * no arg */ static void sscop_inrec_recover(struct sscop *sscop, struct sscop_msg *unused __unused) { if(!sscop->clear_buffers) { MSGQ_CLEAR(&sscop->xbuf); } m_initialize_mr(sscop); send_erak(sscop); m_initialize_state(sscop); m_set_data_xfer_timers(sscop); sscop_set_state(sscop, SSCOP_READY); } /* * p 56: IN_REC_PEND && SD PDU queued * no arg */ static void sscop_inrec_pduq(struct sscop *sscop, struct sscop_msg *msg) { sscop_save_signal(sscop, SIG_PDU_Q, msg); } /* * p 56: IN_REC_PEND && AA-RELEASE.request * arg is UU */ static void sscop_inrec_release_req(struct sscop *sscop, struct sscop_msg *uu) { SET_UU(uu_end, uu); sscop->vt_cc = 1; send_end(sscop, 0, sscop->uu_end); TIMER_RESTART(sscop, cc); sscop_set_state(sscop, SSCOP_OUT_DIS_PEND); } /* * p 56: IN_REC_PEND && END PDU * arg is pdu (freed). * no uui */ static void sscop_inrec_end(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); (void)MBUF_STRIP32(msg->m); send_endak(sscop); AAL_UU_SIGNAL(sscop, SSCOP_RELEASE_indication, msg, pdu.sscop_pl, (u_int)pdu.sscop_s); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 56: IN_REC_PEND && RESYNC_REQ */ static void sscop_inrec_sync_req(struct sscop *sscop, struct sscop_msg *uu) { SET_UU(uu_rs, uu); m_clear_transmitter(sscop); sscop->vt_cc = 1; sscop->vt_sq++; m_initialize_mr(sscop); send_rs(sscop, 0, sscop->uu_rs); TIMER_RESTART(sscop, cc); sscop_set_state(sscop, SSCOP_OUT_RESYNC_PEND); } /* * p 57: IN_REC_PEND && ENDAK PDU * arg is pdu (freed) */ static void sscop_inrec_endak(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'F', 0); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); SSCOP_MSG_FREE(msg); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 57: IN_REC_PEND && BGREJ PDU * arg is pdu (freed) */ static void sscop_inrec_bgrej(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'D', 0); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); SSCOP_MSG_FREE(msg); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 57: IN_REC_PEND && USTAT PDU * arg is pdu (freed) */ static void sscop_inrec_ustat(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'I', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); SSCOP_MSG_FREE(msg); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 57: IN_REC_PEND && STAT PDU * arg is pdu (freed) */ static void sscop_inrec_stat(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'H', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); SSCOP_MSG_FREE(msg); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 57: IN_REC_PEND && POLL PDU * arg is pdu (freed) */ static void sscop_inrec_poll(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'G', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); SSCOP_MSG_FREE(msg); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 57: IN_REC_PEND && SD PDU * arg is pdu (freed) */ static void sscop_inrec_sd(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'A', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); SSCOP_MSG_FREE(msg); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 58: IN_REC_PEND && RSAK PDU * arg is pdu (freed). */ static void sscop_inrec_rsak(struct sscop *sscop, struct sscop_msg *msg) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'K', 0); } /* * p 58: IN_REC_PEND && RS PDU * arg is pdu (freed). */ static void sscop_inrec_rs(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { SSCOP_MSG_FREE(msg); MAAL_ERROR(sscop, 'J', 0); return; } (void)MBUF_STRIP32(msg->m); sscop->vt_ms = pdu.sscop_ns; AAL_UU_SIGNAL(sscop, SSCOP_RESYNC_indication, msg, pdu.sscop_pl, 0); sscop_set_state(sscop, SSCOP_IN_RESYNC_PEND); } /* * p 59: IN_REC_PEND && ER PDU * arg is pdu (freed) */ static void sscop_inrec_er(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(!m_detect_retransmission(sscop, msg)) { MAAL_ERROR(sscop, 'L', 0); } SSCOP_MSG_FREE(msg); } /* * p 59: IN_REC_PEND && BGN PDU * arg is pdu (freed). * no uui */ static void sscop_inrec_bgn(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { MAAL_ERROR(sscop, 'B', 0); SSCOP_MSG_FREE(msg); return; } (void)MBUF_STRIP32(msg->m); sscop->vt_ms = pdu.sscop_ns; AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 0); AAL_UU_SIGNAL(sscop, SSCOP_ESTABLISH_indication, msg, pdu.sscop_pl, 0); sscop_set_state(sscop, SSCOP_IN_PEND); } /* * p 59: IN_REC_PEND && BGAK PDU * arg is pdu (freed) * no uui */ static void sscop_inrec_bgak(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'C', 0); SSCOP_MSG_FREE(msg); } /* * p 59: IN_REC_PEND && ERAK PDU * arg is pdu (freed) * no uui */ static void sscop_inrec_erak(struct sscop *sscop, struct sscop_msg *msg) { MAAL_ERROR(sscop, 'M', 0); SSCOP_MSG_FREE(msg); } /* * p 60: READY && RESYNC request * arg is UU */ static void sscop_ready_sync_req(struct sscop *sscop, struct sscop_msg *uu) { SET_UU(uu_rs, uu); m_reset_data_xfer_timers(sscop); sscop->vt_cc = 1; sscop->vt_sq++; m_initialize_mr(sscop); send_rs(sscop, 0, sscop->uu_rs); m_release_buffers(sscop); TIMER_RESTART(sscop, cc); sscop_set_state(sscop, SSCOP_OUT_RESYNC_PEND); } /* * p 60: READY && AA-RELEASE.request * arg is uu. */ static void sscop_ready_release_req(struct sscop *sscop, struct sscop_msg *uu) { SET_UU(uu_end, uu); m_reset_data_xfer_timers(sscop); sscop->vt_cc = 1; send_end(sscop, 0, sscop->uu_end); m_prepare_retrieval(sscop); TIMER_RESTART(sscop, cc); sscop_set_state(sscop, SSCOP_OUT_DIS_PEND); } /* * p 61: READY && ER PDU * arg is pdu (freed). */ static void sscop_ready_er(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { TIMER_RESTART(sscop, nr); send_erak(sscop); } else { m_reset_data_xfer_timers(sscop); sscop->vt_ms = pdu.sscop_ns; m_prepare_recovery(sscop); m_deliver_data(sscop); AAL_SIG(sscop, SSCOP_RECOVER_indication); sscop_set_state(sscop, SSCOP_IN_REC_PEND); } SSCOP_MSG_FREE(msg); } /* * p 61: READY && BGN PDU * arg is pdu (freed) */ static void sscop_ready_bgn(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { TIMER_RESTART(sscop, nr); send_bgak(sscop, sscop->uu_bgak); SSCOP_MSG_FREE(msg); return; } (void)MBUF_STRIP32(msg->m); m_reset_data_xfer_timers(sscop); sscop->vt_ms = pdu.sscop_ns; AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 0); AAL_UU_SIGNAL(sscop, SSCOP_ESTABLISH_indication, msg, pdu.sscop_pl, 0); m_prepare_retrieval(sscop); sscop_set_state(sscop, SSCOP_IN_PEND); } /* * p 62: READY && ENDAK PDU * arg is pdu (freed) */ static void sscop_ready_endak(struct sscop *sscop, struct sscop_msg *msg) { m_reset_data_xfer_timers(sscop); MAAL_ERROR(sscop, 'F', 0); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); m_prepare_retrieval(sscop); SSCOP_MSG_FREE(msg); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 62: READY && BGREJ PDU * arg is pdu (freed) */ static void sscop_ready_bgrej(struct sscop *sscop, struct sscop_msg *msg) { m_reset_data_xfer_timers(sscop); MAAL_ERROR(sscop, 'D', 0); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); m_prepare_retrieval(sscop); SSCOP_MSG_FREE(msg); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 62: READY && RS PDU * arg is pdu (freed) */ static void sscop_ready_rs(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); if(m_detect_retransmission(sscop, msg)) { SSCOP_MSG_FREE(msg); TIMER_RESTART(sscop, nr); send_rsak(sscop); return; } (void)MBUF_STRIP32(msg->m); m_reset_data_xfer_timers(sscop); sscop->vt_ms = pdu.sscop_ns; AAL_UU_SIGNAL(sscop, SSCOP_RESYNC_indication, msg, pdu.sscop_pl, 0); m_prepare_retrieval(sscop); sscop_set_state(sscop, SSCOP_IN_RESYNC_PEND); } /* * p 62: READY && END PDU * arg is pdu (freed) */ static void sscop_ready_end(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); (void)MBUF_STRIP32(msg->m); m_reset_data_xfer_timers(sscop); send_endak(sscop); AAL_UU_SIGNAL(sscop, SSCOP_RELEASE_indication, msg, pdu.sscop_pl, (u_int)pdu.sscop_s); m_prepare_retrieval(sscop); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 63: READY && POLL expiry */ static void sscop_ready_tpoll(struct sscop *sscop, struct sscop_msg *unused __unused) { sscop->vt_ps++; send_poll(sscop); sscop->vt_pd = 0; m_set_poll_timer(sscop); } /* * p 63: READY && KEEP_ALIVE expiry */ static void sscop_ready_tka(struct sscop *sscop, struct sscop_msg *unused __unused) { sscop->vt_ps++; send_poll(sscop); sscop->vt_pd = 0; m_set_poll_timer(sscop); } /* * p 63: READY && IDLE expiry */ static void sscop_ready_tidle(struct sscop *sscop, struct sscop_msg *unused __unused) { TIMER_RESTART(sscop, nr); sscop->vt_ps++; send_poll(sscop); sscop->vt_pd = 0; m_set_poll_timer(sscop); } /* * p 63: READY && NO_RESPONSE expiry * no arg */ static void sscop_ready_nr(struct sscop *sscop, struct sscop_msg *unused __unused) { m_reset_data_xfer_timers(sscop); MAAL_ERROR(sscop, 'P', 0); FREE_UU(uu_end); send_end(sscop, 1, NULL); AAL_DATA(sscop, SSCOP_RELEASE_indication, NULL, 1); m_prepare_retrieval(sscop); sscop_set_state(sscop, SSCOP_IDLE); } /* * p 63: READY && AA-DATA.request * arg is message (queued). */ static void sscop_ready_userdata(struct sscop *sscop, struct sscop_msg *msg) { MSGQ_APPEND(&sscop->xq, msg); sscop_signal(sscop, SIG_PDU_Q, msg); } /* * p 64: READY && SD PDU queued up * arg is unused. */ static void sscop_ready_pduq(struct sscop *sscop, struct sscop_msg *unused __unused) { struct sscop_msg *msg; if(sscop->rxq != 0) { TAILQ_FOREACH(msg, &sscop->xbuf, link) if(msg->rexmit) break; ASSERT(msg != NULL); msg->rexmit = 0; sscop->rxq--; send_sd(sscop, msg->m, msg->seqno); msg->poll_seqno = sscop->vt_ps; if(sscop->poll_after_rex && sscop->rxq == 0) goto poll; /* -> A */ else goto maybe_poll; /* -> B */ } if(MSGQ_EMPTY(&sscop->xq)) return; if(sscop->vt_s >= sscop->vt_ms) { /* Send windows closed */ TIMER_STOP(sscop, idle); TIMER_RESTART(sscop, nr); goto poll; /* -> A */ } else { msg = MSGQ_GET(&sscop->xq); msg->seqno = sscop->vt_s; send_sd(sscop, msg->m, msg->seqno); msg->poll_seqno = sscop->vt_ps; sscop->vt_s++; MSGQ_APPEND(&sscop->xbuf, msg); goto maybe_poll; /* -> B */ } /* * p 65: Poll handling */ maybe_poll: /* label B */ sscop->vt_pd++; if(TIMER_ISACT(sscop, poll)) { if(sscop->vt_pd < sscop->maxpd) return; } else { if(TIMER_ISACT(sscop, idle)) { TIMER_STOP(sscop, idle); TIMER_RESTART(sscop, nr); } else { TIMER_STOP(sscop, ka); } if(sscop->vt_pd < sscop->maxpd) { TIMER_RESTART(sscop, poll); return; } } poll: /* label A */ sscop->vt_ps++; send_poll(sscop); sscop->vt_pd = 0; TIMER_RESTART(sscop, poll); } /* * p 67: common recovery start */ static void sscop_recover(struct sscop *sscop) { sscop->vt_cc = 1; sscop->vt_sq++; m_initialize_mr(sscop); send_er(sscop); m_prepare_recovery(sscop); TIMER_RESTART(sscop, cc); sscop_set_state(sscop, SSCOP_OUT_REC_PEND); } /* * p 66: READY && SD PDU * arg is received message. */ static void sscop_ready_sd(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; u_int sn; pdu.sscop_null = MBUF_STRIP32(msg->m); msg->seqno = pdu.sscop_ns; /* Fix padding */ MBUF_UNPAD(msg->m, pdu.sscop_pl); if(msg->seqno >= sscop->vr_mr) { /* message outside window */ if(sscop->vr_h < sscop->vr_mr) { send_ustat(sscop, sscop->vr_h, sscop->vr_mr, -1); sscop->vr_h = sscop->vr_mr; } SSCOP_MSG_FREE(msg); return; } if(msg->seqno == sscop->vr_r) { if(msg->seqno == sscop->vr_h) { sscop->vr_r = msg->seqno + 1; sscop->vr_h = msg->seqno + 1; AAL_DATA(sscop, SSCOP_DATA_indication, msg->m, msg->seqno); msg->m = NULL; SSCOP_MSG_FREE(msg); return; } for(;;) { AAL_DATA(sscop, SSCOP_DATA_indication, msg->m, msg->seqno); msg->m = NULL; SSCOP_MSG_FREE(msg); sscop->vr_r++; if((msg = MSGQ_PEEK(&sscop->rbuf)) == NULL) break; sn = msg->seqno; ASSERT(sn >= sscop->vr_r); if(sn != sscop->vr_r) break; msg = MSGQ_GET(&sscop->rbuf); } return; } /* Messages were lost */ /* XXX Flow control */ if(msg->seqno == sscop->vr_h) { QINSERT(&sscop->rbuf, msg); sscop->vr_h++; return; } if(sscop->vr_h < msg->seqno) { QINSERT(&sscop->rbuf, msg); send_ustat(sscop, sscop->vr_h, msg->seqno, -1); sscop->vr_h = msg->seqno + 1; return; } if(QFIND(&sscop->rbuf, msg->seqno) == NULL) { QINSERT(&sscop->rbuf, msg); return; } /* error: start recovery */ SSCOP_MSG_FREE(msg); m_reset_data_xfer_timers(sscop); MAAL_ERROR(sscop, 'Q', 0); sscop_recover(sscop); } /* * p 67: READY && POLL PDU */ static void sscop_ready_poll(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; union seqno seqno; u_int sn, nps; struct SSCOP_MBUF_T *m; pdu.sscop_null = MBUF_STRIP32(msg->m); seqno.sscop_null = MBUF_STRIP32(msg->m); if((u_int)pdu.sscop_ns < sscop->vr_h) { SSCOP_MSG_FREE(msg); m_reset_data_xfer_timers(sscop); MAAL_ERROR(sscop, 'Q', 0); sscop_recover(sscop); return; } nps = seqno.sscop_n; if((u_int)pdu.sscop_ns > sscop->vr_mr) sscop->vr_h = sscop->vr_mr; else sscop->vr_h = pdu.sscop_ns; SSCOP_MSG_FREE(msg); /* build stat pdu */ if((m = MBUF_ALLOC(sscop->maxstat * 4 + 12)) == NULL) { FAILURE("sscop: cannot allocate STAT"); return; } sn = sscop->vr_r; while(sn != sscop->vr_h) { /* loop through burst we already have */ for(;;) { if(sn >= sscop->vr_h) { seqno.sscop_null = 0; seqno.sscop_n = sn; MBUF_APPEND32(m, seqno.sscop_null); goto out; } if(QFIND(&sscop->rbuf, sn) == NULL) break; sn++; } /* start of a hole */ seqno.sscop_null = 0; seqno.sscop_n = sn; MBUF_APPEND32(m, seqno.sscop_null); if(MBUF_LEN(m)/4 >= sscop->maxstat) { send_stat(sscop, nps, m); if((m = MBUF_ALLOC(sscop->maxstat * 4 + 12)) == NULL) { FAILURE("sscop: cannot allocate STAT"); return; } seqno.sscop_null = 0; seqno.sscop_n = sn; MBUF_APPEND32(m, seqno.sscop_null); } do { sn++; } while(sn < sscop->vr_h && !QFIND(&sscop->rbuf, sn)); seqno.sscop_null = 0; seqno.sscop_n = sn; MBUF_APPEND32(m, seqno.sscop_null); } out: send_stat(sscop, nps, m); } /* * p 69: READY && USTAT PDU * arg is msg (freed) */ static void sscop_ready_ustat(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; union seqno nmr, sq1, sq2; u_int cnt; pdu.sscop_null = MBUF_STRIP32(msg->m); nmr.sscop_null = MBUF_STRIP32(msg->m); sq2.sscop_null = MBUF_STRIP32(msg->m); sq1.sscop_null = MBUF_STRIP32(msg->m); SSCOP_MSG_FREE(msg); cnt = sq1.sscop_n - sq2.sscop_n; if((u_int)pdu.sscop_ns < sscop->vt_a || (u_int)pdu.sscop_ns >= sscop->vt_s) { VERBERR(sscop, SSCOP_DBG_ERR, (sscop, sscop->aarg, "USTAT: N(R) outside VT(A)...VT(S)-1: N(R)=%u VT(A)=%u " "VT(S)=%u", (u_int)pdu.sscop_ns, sscop->vt_a, sscop->vt_s)); goto err_f; } /* Acknowledge all messages between VT(A) and N(R)-1. N(R) is the new * next in sequence-SD-number of the receiver and means, it has all * messages below N(R). Remove all message below N(R) from the * transmission buffer. It may already be removed because of an * earlier selective ACK in a STAT message. */ while((msg = MSGQ_PEEK(&sscop->xbuf)) != NULL && msg->seqno < (u_int)pdu.sscop_ns) { ASSERT(msg->seqno >= sscop->vt_a); MSGQ_REMOVE(&sscop->xbuf, msg); SSCOP_MSG_FREE(msg); } /* Update the in-sequence acknowledge and the send window */ sscop->vt_a = pdu.sscop_ns; sscop->vt_ms = nmr.sscop_n; /* check, that the range of requested re-transmissions is between * the in-sequence-ack and the highest up-to-now transmitted SD */ if(sq1.sscop_n >= sq2.sscop_n || (u_int)sq1.sscop_n < sscop->vt_a || (u_int)sq2.sscop_n >= sscop->vt_s) { VERBERR(sscop, SSCOP_DBG_ERR, (sscop, sscop->aarg, "USTAT: seq1 or seq2 outside VT(A)...VT(S)-1 or seq1>=seq2:" " seq1=%u seq2=%u VT(A)=%u VT(S)=%u", sq1.sscop_n, sq2.sscop_n, sscop->vt_a, sscop->vt_s)); goto err_f; } /* * Retransmit all messages from seq1 to seq2-1 */ do { /* * The message may not be in the transmit buffer if it was * already acked by a STAT. This means, the receiver is * confused. */ if((msg = QFIND(&sscop->xbuf, sq1.sscop_n)) == NULL) { VERBERR(sscop, SSCOP_DBG_ERR, (sscop, sscop->aarg, "USTAT: message %u not found in xmit buffer", sq1.sscop_n)); goto err_f; } /* * If it is not yet in the re-transmission queue, put it there */ if(!msg->rexmit) { msg->rexmit = 1; sscop->rxq++; sscop_signal(sscop, SIG_PDU_Q, msg); } sq1.sscop_n++; } while(sq1.sscop_n != sq2.sscop_n); /* * report the re-transmission to the management */ MAAL_ERROR(sscop, 'V', cnt); return; err_f: m_reset_data_xfer_timers(sscop); MAAL_ERROR(sscop, 'T', 0); sscop_recover(sscop); } /* * p 70: READY && STAT PDU * arg is msg (freed). */ static void sscop_ready_stat(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; union seqno nps, nmr; u_int len, seq1, seq2, cnt; struct sscop_msg *m; pdu.sscop_null = MBUF_STRIP32(msg->m); nmr.sscop_null = MBUF_STRIP32(msg->m); nps.sscop_null = MBUF_STRIP32(msg->m); len = MBUF_LEN(msg->m) / 4; if((u_int)nps.sscop_n < sscop->vt_pa || (u_int)nps.sscop_n > sscop->vt_ps) { SSCOP_MSG_FREE(msg); m_reset_data_xfer_timers(sscop); MAAL_ERROR(sscop, 'R', 0); sscop_recover(sscop); return; } if((u_int)pdu.sscop_ns < sscop->vt_a || (u_int)pdu.sscop_ns > sscop->vt_s) { /* * The in-sequence acknowledge, i.e. the receivers's next * expected in-sequence msg is outside the window between * the transmitters in-sequence ack and highest seqno - * the receiver seems to be confused. */ VERBERR(sscop, SSCOP_DBG_ERR, (sscop, sscop->aarg, "STAT: N(R) outside VT(A)...VT(S)-1: N(R)=%u VT(A)=%u " "VT(S)=%u", (u_int)pdu.sscop_ns, sscop->vt_a, sscop->vt_s)); err_H: SSCOP_MSG_FREE(msg); m_reset_data_xfer_timers(sscop); MAAL_ERROR(sscop, 'S', 0); sscop_recover(sscop); return; } /* Acknowledge all messages between VT(A) and N(R)-1. N(R) is the new * next in sequence-SD-number of the receiver and means, it has all * messages below N(R). Remove all message below N(R) from the * transmission buffer. It may already be removed because of an * earlier selective ACK in a STAT message. */ while((m = MSGQ_PEEK(&sscop->xbuf)) != NULL && m->seqno < (u_int)pdu.sscop_ns) { ASSERT(m->seqno >= sscop->vt_a); MSGQ_REMOVE(&sscop->xbuf, m); SSCOP_MSG_FREE(m); } /* * Update in-sequence ack, poll-ack and send window. */ sscop->vt_a = pdu.sscop_ns; sscop->vt_pa = nps.sscop_n; sscop->vt_ms = nmr.sscop_n; cnt = 0; if(len > 1) { seq1 = MBUF_GET32(msg->m); len--; if(seq1 >= sscop->vt_s) { VERBERR(sscop, SSCOP_DBG_ERR, (sscop, sscop->aarg, "STAT: seq1 >= VT(S): seq1=%u VT(S)=%u", seq1, sscop->vt_s)); goto err_H; } for(;;) { seq2 = MBUF_GET32(msg->m); len--; if(seq1 >= seq2 || seq2 > sscop->vt_s) { VERBERR(sscop, SSCOP_DBG_ERR, (sscop, sscop->aarg, "STAT: seq1 >= seq2 or " "seq2 > VT(S): seq1=%u seq2=%u VT(S)=%u", seq1, seq2, sscop->vt_s)); goto err_H; } do { /* * The receiver requests the re-transmission * of some message, but has acknowledged it * already in an earlier STAT (it isn't in the * transmitt buffer anymore). */ if((m = QFIND(&sscop->xbuf, seq1)) == NULL) { VERBERR(sscop, SSCOP_DBG_ERR, (sscop, sscop->aarg, "STAT: message" " %u not found in xmit buffer", seq1)); goto err_H; } if(m->poll_seqno < (u_int)nps.sscop_n && (u_int)nps.sscop_n <= sscop->vt_ps) if(!m->rexmit) { m->rexmit = 1; sscop->rxq++; cnt++; sscop_signal(sscop, SIG_PDU_Q, msg); } } while(++seq1 < seq2); if(len == 0) break; seq2 = MBUF_GET32(msg->m); len--; if(seq1 >= seq2 || seq2 > sscop->vt_s) { VERBERR(sscop, SSCOP_DBG_ERR, (sscop, sscop->aarg, "STAT: seq1 >= seq2 or " "seq2 > VT(S): seq1=%u seq2=%u VT(S)=%u", seq1, seq2, sscop->vt_s)); goto err_H; } /* OK now the sucessful transmitted messages. Note, that * some messages may already be out of the buffer because * of earlier STATS */ do { if(sscop->clear_buffers) { if((m = QFIND(&sscop->xbuf, seq1)) != NULL) { MSGQ_REMOVE(&sscop->xbuf, m); SSCOP_MSG_FREE(m); } } } while(++seq1 != seq2); if(len == 0) break; } MAAL_ERROR(sscop, 'V', cnt); } SSCOP_MSG_FREE(msg); /* label L: */ if(sscop->vt_s >= sscop->vt_ms) { /* * The receiver has closed the window: report to management */ if(sscop->credit) { sscop->credit = 0; MAAL_ERROR(sscop, 'W', 0); } } else if(!sscop->credit) { /* * The window was forcefully closed above, but * now re-opened. Report to management. */ sscop->credit = 1; MAAL_ERROR(sscop, 'X', 0); } if(TIMER_ISACT(sscop, poll)) { TIMER_RESTART(sscop, nr); } else if(!TIMER_ISACT(sscop, idle)) { TIMER_STOP(sscop, ka); TIMER_STOP(sscop, nr); TIMER_RESTART(sscop, idle); } } /* * P. 73: any state & UDATA_REQUEST * arg is pdu (queued) */ static void sscop_udata_req(struct sscop *sscop, struct sscop_msg *msg) { MSGQ_APPEND(&sscop->uxq, msg); sscop_signal(sscop, SIG_UPDU_Q, msg); } /* * P. 73: any state & MDATA_REQUEST * arg is pdu (queued) */ static void sscop_mdata_req(struct sscop *sscop, struct sscop_msg *msg) { MSGQ_APPEND(&sscop->mxq, msg); sscop_signal(sscop, SIG_MPDU_Q, msg); } /* * P. 74: any state & UDATA queued * no arg. */ static void sscop_upduq(struct sscop *sscop, struct sscop_msg *unused __unused) { struct sscop_msg *msg; if(sscop->ll_busy) return; while((msg = MSGQ_GET(&sscop->uxq)) != NULL) { send_ud(sscop, msg->m); msg->m = NULL; SSCOP_MSG_FREE(msg); } } /* * P. 74: any state & MDATA queued * no arg. */ static void sscop_mpduq(struct sscop *sscop, struct sscop_msg *unused __unused) { struct sscop_msg *msg; if(sscop->ll_busy) return; while((msg = MSGQ_GET(&sscop->mxq)) != NULL) { send_md(sscop, msg->m); msg->m = NULL; SSCOP_MSG_FREE(msg); } } /* * p 73: MD PDU * arg is PDU */ static void sscop_md(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); MBUF_UNPAD(msg->m, pdu.sscop_pl); MAAL_DATA(sscop, msg->m); msg->m = NULL; SSCOP_MSG_FREE(msg); } /* * p 73: UD PDU * arg is PDU */ static void sscop_ud(struct sscop *sscop, struct sscop_msg *msg) { union pdu pdu; pdu.sscop_null = MBUF_STRIP32(msg->m); MBUF_UNPAD(msg->m, pdu.sscop_pl); AAL_DATA(sscop, SSCOP_UDATA_indication, msg->m, 0); msg->m = NULL; SSCOP_MSG_FREE(msg); } /* * p 33: IDLE & RETRIEVE * p 39: IN_PEND & RETRIEVE * p 42: OUT_DIS_PEND & RETRIEVE * p 48: IN_RESYNC_PEND & RETRIEVE * p 53: REC_PEND & RETRIEVE * p 58: IN_REC_PEND & RETRIEVE */ static void sscop_retrieve(struct sscop *sscop, struct sscop_msg *msg) { m_data_retrieval(sscop, msg->rexmit); SSCOP_MSG_FREE(msg); } /************************************************************/ /* * GENERAL EVENT HANDLING */ /* * State/event matrix. * * Entries marked with Z are not specified in Q.2110, but are added for * the sake of stability. */ static struct { void (*func)(struct sscop *, struct sscop_msg *); int (*cond)(struct sscop *); } state_matrix[SSCOP_NSTATES][SIG_NUM] = { /* SSCOP_IDLE */ { /* SIG_BGN */ { sscop_idle_bgn, NULL }, /* SIG_BGAK */ { sscop_idle_bgak, NULL }, /* SIG_END */ { sscop_idle_end, NULL }, /* SIG_ENDAK */ { sscop_ignore_pdu, NULL }, /* SIG_RS */ { sscop_idle_rs, NULL }, /* SIG_RSAK */ { sscop_idle_rsak, NULL }, /* SIG_BGREJ */ { sscop_idle_bgrej, NULL }, /* SIG_SD */ { sscop_idle_sd, NULL }, /* SIG_ER */ { sscop_idle_er, NULL }, /* SIG_POLL */ { sscop_idle_poll, NULL }, /* SIG_STAT */ { sscop_idle_stat, NULL }, /* SIG_USTAT */ { sscop_idle_ustat, NULL }, /* SIG_UD */ { sscop_ud, NULL }, /* SIG_MD */ { sscop_md, NULL }, /* SIG_ERAK */ { sscop_idle_erak, NULL }, /* SIG_T_CC */ { NULL, NULL }, /* SIG_T_POLL */ { NULL, NULL }, /* SIG_T_KA */ { NULL, NULL }, /* SIG_T_NR */ { NULL, NULL }, /* SIG_T_IDLE */ { NULL, NULL }, /* SIG_PDU_Q */ { sscop_flush_pduq, NULL }, /* SIG_USER_DATA */ { NULL, NULL }, /* SIG_ESTAB_REQ */ { sscop_idle_establish_req, NULL }, /* SIG_ESTAB_RESP */ { NULL, NULL }, /* SIG_RELEASE_REQ */ { NULL, NULL }, /* SIG_RECOVER */ { NULL, NULL }, /* SIG_SYNC_REQ */ { NULL, NULL }, /* SIG_SYNC_RESP */ { NULL, NULL }, /* SIG_UDATA */ { sscop_udata_req, NULL }, /* SIG_MDATA */ { sscop_mdata_req, NULL }, /* SIG_UPDU_Q */ { sscop_upduq, NULL }, /* SIG_MPDU_Q */ { sscop_mpduq, NULL }, /* SIG_RETRIEVE */ { sscop_retrieve, NULL }, }, /* SSCOP_OUT_PEND */ { /* SIG_BGN */ { sscop_outpend_bgn, NULL }, /* SIG_BGAK */ { sscop_outpend_bgak, NULL }, /* SIG_END */ { sscop_ignore_pdu, NULL }, /* SIG_ENDAK */ { sscop_ignore_pdu, NULL }, /* SIG_RS */ { sscop_ignore_pdu, NULL }, /* SIG_RSAK */ { sscop_ignore_pdu, NULL }, /* SIG_BGREJ */ { sscop_outpend_bgrej, NULL }, /* SIG_SD */ { sscop_ignore_pdu, NULL }, /* SIG_ER */ { sscop_ignore_pdu, NULL }, /* SIG_POLL */ { sscop_ignore_pdu, NULL }, /* SIG_STAT */ { sscop_ignore_pdu, NULL }, /* SIG_USTAT */ { sscop_ignore_pdu, NULL }, /* SIG_UD */ { sscop_ud, NULL }, /* SIG_MD */ { sscop_md, NULL }, /* SIG_ERAK */ { sscop_ignore_pdu, NULL }, /* SIG_T_CC */ { sscop_outpend_tcc, NULL }, /* SIG_T_POLL */ { NULL, NULL }, /* SIG_T_KA */ { NULL, NULL }, /* SIG_T_NR */ { NULL, NULL }, /* SIG_T_IDLE */ { NULL, NULL }, /* SIG_PDU_Q */ { sscop_flush_pduq, NULL }, /* SIG_USER_DATA */ { NULL, NULL }, /* SIG_ESTAB_REQ */ { NULL, NULL }, /* SIG_ESTAB_RESP */ { NULL, NULL }, /* SIG_RELEASE_REQ */ { sscop_outpend_release_req, NULL }, /* SIG_RECOVER */ { NULL, NULL }, /* SIG_SYNC_REQ */ { NULL, NULL }, /* SIG_SYNC_RESP */ { NULL, NULL }, /* SIG_UDATA */ { sscop_udata_req, NULL }, /* SIG_MDATA */ { sscop_mdata_req, NULL }, /* SIG_UPDU_Q */ { sscop_upduq, NULL }, /* SIG_MPDU_Q */ { sscop_mpduq, NULL }, /* SIG_RETRIEVE */ { NULL, NULL }, }, /* SSCOP_IN_PEND */ { /* SIG_BGN */ { sscop_inpend_bgn, NULL }, /* SIG_BGAK */ { sscop_inpend_bgak, NULL }, /* SIG_END */ { sscop_inpend_end, NULL }, /* SIG_ENDAK */ { sscop_inpend_endak, NULL }, /* SIG_RS */ { sscop_inpend_rs, NULL }, /* SIG_RSAK */ { sscop_inpend_rsak, NULL }, /* SIG_BGREJ */ { sscop_inpend_bgrej, NULL }, /* SIG_SD */ { sscop_inpend_sd, NULL }, /* SIG_ER */ { sscop_inpend_er, NULL }, /* SIG_POLL */ { sscop_inpend_poll, NULL }, /* SIG_STAT */ { sscop_inpend_stat, NULL }, /* SIG_USTAT */ { sscop_inpend_ustat, NULL }, /* SIG_UD */ { sscop_ud, NULL }, /* SIG_MD */ { sscop_md, NULL }, /* SIG_ERAK */ { sscop_inpend_erak, NULL }, /* SIG_T_CC */ { NULL, NULL }, /* SIG_T_POLL */ { NULL, NULL }, /* SIG_T_KA */ { NULL, NULL }, /* SIG_T_NR */ { NULL, NULL }, /* SIG_T_IDLE */ { NULL, NULL }, /* SIG_PDU_Q */ { sscop_flush_pduq, NULL }, /* SIG_USER_DATA */ { NULL, NULL }, /* SIG_ESTAB_REQ */ { NULL, NULL }, /* SIG_ESTAB_RESP */ { sscop_inpend_establish_resp, NULL }, /* SIG_RELEASE_REQ */ { sscop_inpend_release_req, NULL }, /* SIG_RECOVER */ { NULL, NULL }, /* SIG_SYNC_REQ */ { NULL, NULL }, /* SIG_SYNC_RESP */ { NULL, NULL }, /* SIG_UDATA */ { sscop_udata_req, NULL }, /* SIG_MDATA */ { sscop_mdata_req, NULL }, /* SIG_UPDU_Q */ { sscop_upduq, NULL }, /* SIG_MPDU_Q */ { sscop_mpduq, NULL }, /* SIG_RETRIEVE */ { sscop_retrieve, NULL }, }, /* SSCOP_OUT_DIS_PEND */ { /* SIG_BGN */ { sscop_outdis_bgn, NULL }, /* SIG_BGAK */ { sscop_ignore_pdu, NULL }, /* SIG_END */ { sscop_outdis_end, NULL }, /* SIG_ENDAK */ { sscop_outdis_endak, NULL }, /* SIG_RS */ { sscop_ignore_pdu, NULL }, /* SIG_RSAK */ { sscop_ignore_pdu, NULL }, /* SIG_BGREJ */ { sscop_outdis_endak, NULL }, /* SIG_SD */ { sscop_ignore_pdu, NULL }, /* SIG_ER */ { sscop_ignore_pdu, NULL }, /* SIG_POLL */ { sscop_ignore_pdu, NULL }, /* SIG_STAT */ { sscop_ignore_pdu, NULL }, /* SIG_USTAT */ { sscop_ignore_pdu, NULL }, /* SIG_UD */ { sscop_ud, NULL }, /* SIG_MD */ { sscop_md, NULL }, /* SIG_ERAK */ { sscop_ignore_pdu, NULL }, /* SIG_T_CC */ { sscop_outdis_cc, NULL }, /* SIG_T_POLL */ { NULL, NULL }, /* SIG_T_KA */ { NULL, NULL }, /* SIG_T_NR */ { NULL, NULL }, /* SIG_T_IDLE */ { NULL, NULL }, /* SIG_PDU_Q */ { sscop_flush_pduq, NULL }, /* SIG_USER_DATA */ { NULL, NULL }, /* SIG_ESTAB_REQ */ { sscop_outdis_establish_req, NULL }, /* SIG_ESTAB_RESP */ { NULL, NULL }, /* SIG_RELEASE_REQ */ { NULL, NULL }, /* SIG_RECOVER */ { NULL, NULL }, /* SIG_SYNC_REQ */ { NULL, NULL }, /* SIG_SYNC_RESP */ { NULL, NULL }, /* SIG_UDATA */ { sscop_udata_req, NULL }, /* SIG_MDATA */ { sscop_mdata_req, NULL }, /* SIG_UPDU_Q */ { sscop_upduq, NULL }, /* SIG_MPDU_Q */ { sscop_mpduq, NULL }, /* SIG_RETRIEVE */ { sscop_retrieve, NULL }, }, /* SSCOP_OUT_RESYNC_PEND */ { /* SIG_BGN */ { sscop_outsync_bgn, NULL }, /* SIG_BGAK */ { sscop_ignore_pdu, NULL }, /* SIG_END */ { sscop_outsync_end, NULL }, /* SIG_ENDAK */ { sscop_outsync_endak, NULL }, /* SIG_RS */ { sscop_outsync_rs, NULL }, /* SIG_RSAK */ { sscop_outsync_rsak, NULL }, /* SIG_BGREJ */ { sscop_outsync_bgrej, NULL }, /* SIG_SD */ { sscop_ignore_pdu, NULL }, /* SIG_ER */ { sscop_ignore_pdu, NULL }, /* SIG_POLL */ { sscop_ignore_pdu, NULL }, /* SIG_STAT */ { sscop_ignore_pdu, NULL }, /* SIG_USTAT */ { sscop_ignore_pdu, NULL }, /* SIG_UD */ { sscop_ud, NULL }, /* SIG_MD */ { sscop_md, NULL }, /* SIG_ERAK */ { sscop_ignore_pdu, NULL }, /* SIG_T_CC */ { sscop_outsync_cc, NULL }, /* SIG_T_POLL */ { NULL, NULL }, /* SIG_T_KA */ { NULL, NULL }, /* SIG_T_NR */ { NULL, NULL }, /* SIG_T_IDLE */ { NULL, NULL }, /* SIG_PDU_Q */ { sscop_flush_pduq, NULL }, /* SIG_USER_DATA */ { NULL, NULL }, /* SIG_ESTAB_REQ */ { NULL, NULL }, /* SIG_ESTAB_RESP */ { NULL, NULL }, /* SIG_RELEASE_REQ */ { sscop_outsync_release_req, NULL }, /* SIG_RECOVER */ { NULL, NULL }, /* SIG_SYNC_REQ */ { NULL, NULL }, /* SIG_SYNC_RESP */ { NULL, NULL }, /* SIG_UDATA */ { sscop_udata_req, NULL }, /* SIG_MDATA */ { sscop_mdata_req, NULL }, /* SIG_UPDU_Q */ { sscop_upduq, NULL }, /* SIG_MPDU_Q */ { sscop_mpduq, NULL }, /* SIG_RETRIEVE */ { NULL, NULL }, }, /* SSCOP_IN_RESYNC_PEND */ { /* SIG_BGN */ { sscop_insync_bgn, NULL }, /* SIG_BGAK */ { sscop_insync_bgak, NULL }, /* SIG_END */ { sscop_insync_end, NULL }, /* SIG_ENDAK */ { sscop_insync_endak, NULL }, /* SIG_RS */ { sscop_insync_rs, NULL }, /* SIG_RSAK */ { sscop_insync_rsak, NULL }, /* SIG_BGREJ */ { sscop_insync_bgrej, NULL }, /* SIG_SD */ { sscop_insync_sd, NULL }, /* SIG_ER */ { sscop_insync_er, NULL }, /* SIG_POLL */ { sscop_insync_poll, NULL }, /* SIG_STAT */ { sscop_insync_stat, NULL }, /* SIG_USTAT */ { sscop_insync_ustat, NULL }, /* SIG_UD */ { sscop_ud, NULL }, /* SIG_MD */ { sscop_md, NULL }, /* SIG_ERAK */ { sscop_insync_erak, NULL }, /* SIG_T_CC */ { NULL, NULL }, /* SIG_T_POLL */ { NULL, NULL }, /* SIG_T_KA */ { NULL, NULL }, /* SIG_T_NR */ { NULL, NULL }, /* SIG_T_IDLE */ { NULL, NULL }, /* SIG_PDU_Q */ { sscop_flush_pduq, NULL }, /* SIG_USER_DATA */ { NULL, NULL }, /* SIG_ESTAB_REQ */ { NULL, NULL }, /* SIG_ESTAB_RESP */ { NULL, NULL }, /* SIG_RELEASE_REQ */ { sscop_insync_release_req, NULL }, /* SIG_RECOVER */ { NULL, NULL }, /* SIG_SYNC_REQ */ { NULL, NULL }, /* SIG_SYNC_RESP */ { sscop_insync_sync_resp, NULL }, /* SIG_UDATA */ { sscop_udata_req, NULL }, /* SIG_MDATA */ { sscop_mdata_req, NULL }, /* SIG_UPDU_Q */ { sscop_upduq, NULL }, /* SIG_MPDU_Q */ { sscop_mpduq, NULL }, /* SIG_RETRIEVE */ { sscop_retrieve, NULL }, }, /* SSCOP_OUT_REC_PEND */ { /* SIG_BGN */ { sscop_outrec_bgn, NULL }, /* SIG_BGAK */ { sscop_outrec_bgak, NULL }, /* SIG_END */ { sscop_outrec_end, NULL }, /* SIG_ENDAK */ { sscop_outrec_endak, NULL }, /* SIG_RS */ { sscop_outrec_rs, NULL }, /* SIG_RSAK */ { sscop_outrec_rsak, NULL }, /* SIG_BGREJ */ { sscop_outrec_bgrej, NULL }, /* SIG_SD */ { sscop_ignore_pdu, NULL }, /* SIG_ER */ { sscop_outrec_er, NULL }, /* SIG_POLL */ { sscop_ignore_pdu, NULL }, /* SIG_STAT */ { sscop_ignore_pdu, NULL }, /* SIG_USTAT */ { sscop_ignore_pdu, NULL }, /* SIG_UD */ { sscop_ud, NULL }, /* SIG_MD */ { sscop_md, NULL }, /* SIG_ERAK */ { sscop_outrec_erak, NULL }, /* SIG_T_CC */ { sscop_outrec_cc, NULL }, /* SIG_T_POLL */ { NULL, NULL }, /* SIG_T_KA */ { NULL, NULL }, /* SIG_T_NR */ { NULL, NULL }, /* SIG_T_IDLE */ { NULL, NULL }, /* SIG_PDU_Q */ { sscop_outrec_pduq, NULL }, /* SIG_USER_DATA */ { sscop_outrec_userdata, NULL }, /* SIG_ESTAB_REQ */ { NULL, NULL }, /* SIG_ESTAB_RESP */ { NULL, NULL }, /* SIG_RELEASE_REQ */ { sscop_outrec_release_req, NULL }, /* SIG_RECOVER */ { NULL, NULL }, /* SIG_SYNC_REQ */ { sscop_outrec_sync_req, NULL }, /* SIG_SYNC_RESP */ { NULL, NULL }, /* SIG_UDATA */ { sscop_udata_req, NULL }, /* SIG_MDATA */ { sscop_mdata_req, NULL }, /* SIG_UPDU_Q */ { sscop_upduq, NULL }, /* SIG_MPDU_Q */ { sscop_mpduq, NULL }, /* SIG_RETRIEVE */ { NULL, NULL }, }, /* SSCOP_REC_PEND */ { /* SIG_BGN */ { sscop_rec_bgn, NULL }, /* SIG_BGAK */ { sscop_rec_bgak, NULL }, /* SIG_END */ { sscop_rec_end, NULL }, /* SIG_ENDAK */ { sscop_rec_endak, NULL }, /* SIG_RS */ { sscop_rec_rs, NULL }, /* SIG_RSAK */ { sscop_rec_rsak, NULL }, /* SIG_BGREJ */ { sscop_rec_bgrej, NULL }, /* SIG_SD */ { sscop_ignore_pdu, NULL }, /* SIG_ER */ { sscop_rec_er, NULL }, /* SIG_POLL */ { sscop_ignore_pdu, NULL }, /* SIG_STAT */ { sscop_rec_stat, NULL }, /* SIG_USTAT */ { sscop_rec_ustat, NULL }, /* SIG_UD */ { sscop_ud, NULL }, /* SIG_MD */ { sscop_md, NULL }, /* SIG_ERAK */ { sscop_ignore_pdu, NULL }, /* SIG_T_CC */ { NULL, NULL }, /* SIG_T_POLL */ { NULL, NULL }, /* SIG_T_KA */ { NULL, NULL }, /* SIG_T_NR */ { NULL, NULL }, /* SIG_T_IDLE */ { NULL, NULL }, /* SIG_PDU_Q */ { sscop_rec_pduq, NULL }, /* SIG_USER_DATA */ { NULL, NULL }, /* SIG_ESTAB_REQ */ { NULL, NULL }, /* SIG_ESTAB_RESP */ { NULL, NULL }, /* SIG_RELEASE_REQ */ { sscop_rec_release_req, NULL }, /* SIG_RECOVER */ { sscop_rec_recover, NULL }, /* SIG_SYNC_REQ */ { sscop_rec_sync_req, NULL }, /* SIG_SYNC_RESP */ { NULL, NULL }, /* SIG_UDATA */ { sscop_udata_req, NULL }, /* SIG_MDATA */ { sscop_mdata_req, NULL }, /* SIG_UPDU_Q */ { sscop_upduq, NULL }, /* SIG_MPDU_Q */ { sscop_mpduq, NULL }, /* SIG_RETRIEVE */ { sscop_retrieve, NULL }, }, /* SSCOP_IN_REC_PEND */ { /* SIG_BGN */ { sscop_inrec_bgn, NULL }, /* SIG_BGAK */ { sscop_inrec_bgak, NULL }, /* SIG_END */ { sscop_inrec_end, NULL }, /* SIG_ENDAK */ { sscop_inrec_endak, NULL }, /* SIG_RS */ { sscop_inrec_rs, NULL }, /* SIG_RSAK */ { sscop_inrec_rsak, NULL }, /* SIG_BGREJ */ { sscop_inrec_bgrej, NULL }, /* SIG_SD */ { sscop_inrec_sd, NULL }, /* SIG_ER */ { sscop_inrec_er, NULL }, /* SIG_POLL */ { sscop_inrec_poll, NULL }, /* SIG_STAT */ { sscop_inrec_stat, NULL }, /* SIG_USTAT */ { sscop_inrec_ustat, NULL }, /* SIG_UD */ { sscop_ud, NULL }, /* SIG_MD */ { sscop_md, NULL }, /* SIG_ERAK */ { sscop_inrec_erak, NULL }, /* SIG_T_CC */ { NULL, NULL }, /* SIG_T_POLL */ { NULL, NULL }, /* SIG_T_KA */ { NULL, NULL }, /* SIG_T_NR */ { NULL, NULL }, /* SIG_T_IDLE */ { NULL, NULL }, /* SIG_PDU_Q */ { sscop_inrec_pduq, NULL }, /* SIG_USER_DATA */ { NULL, NULL }, /* SIG_ESTAB_REQ */ { NULL, NULL }, /* SIG_ESTAB_RESP */ { NULL, NULL }, /* SIG_RELEASE_REQ */ { sscop_inrec_release_req, NULL }, /* SIG_RECOVER */ { sscop_inrec_recover, NULL }, /* SIG_SYNC_REQ */ { sscop_inrec_sync_req, NULL }, /* SIG_SYNC_RESP */ { NULL, NULL }, /* SIG_UDATA */ { sscop_udata_req, NULL }, /* SIG_MDATA */ { sscop_mdata_req, NULL }, /* SIG_UPDU_Q */ { sscop_upduq, NULL }, /* SIG_MPDU_Q */ { sscop_mpduq, NULL }, /* SIG_RETRIEVE */ { sscop_retrieve, NULL }, }, /* SSCOP_READY */ { /* SIG_BGN */ { sscop_ready_bgn, NULL }, /* SIG_BGAK */ { sscop_ignore_pdu, NULL }, /* SIG_END */ { sscop_ready_end, NULL }, /* SIG_ENDAK */ { sscop_ready_endak, NULL }, /* SIG_RS */ { sscop_ready_rs, NULL }, /* SIG_RSAK */ { sscop_ignore_pdu, NULL }, /* SIG_BGREJ */ { sscop_ready_bgrej, NULL }, /* SIG_SD */ { sscop_ready_sd, NULL }, /* SIG_ER */ { sscop_ready_er, NULL }, /* SIG_POLL */ { sscop_ready_poll, NULL }, /* SIG_STAT */ { sscop_ready_stat, NULL }, /* SIG_USTAT */ { sscop_ready_ustat, NULL }, /* SIG_UD */ { sscop_ud, NULL }, /* SIG_MD */ { sscop_md, NULL }, /* SIG_ERAK */ { sscop_ignore_pdu, NULL }, /* SIG_T_CC */ { NULL, NULL }, /* SIG_T_POLL */ { sscop_ready_tpoll, NULL }, /* SIG_T_KA */ { sscop_ready_tka, NULL }, /* SIG_T_NR */ { sscop_ready_nr, NULL }, /* SIG_T_IDLE */ { sscop_ready_tidle, NULL }, /* SIG_PDU_Q */ { sscop_ready_pduq, c_ready_pduq }, /* SIG_USER_DATA */ { sscop_ready_userdata, NULL }, /* SIG_ESTAB_REQ */ { NULL, NULL }, /* SIG_ESTAB_RESP */ { NULL, NULL }, /* SIG_RELEASE_REQ */ { sscop_ready_release_req, NULL }, /* SIG_RECOVER */ { NULL, NULL }, /* SIG_SYNC_REQ */ { sscop_ready_sync_req, NULL }, /* SIG_SYNC_RESP */ { NULL, NULL }, /* SIG_UDATA */ { sscop_udata_req, NULL }, /* SIG_MDATA */ { sscop_mdata_req, NULL }, /* SIG_UPDU_Q */ { sscop_upduq, NULL }, /* SIG_MPDU_Q */ { sscop_mpduq, NULL }, /* SIG_RETRIEVE */ { NULL, NULL }, } }; /* * Try to execute a signal. It is executed if * - it is illegal (in this case it is effectively ignored) * - it has no condition * - its condition is true * If it has a condition and that is false, the function does nothing and * returns 0. * If the signal gets executed, the signal function is responsible to release * the message (if any). */ static int sig_exec(struct sscop *sscop, u_int sig, struct sscop_msg *msg) { void (*func)(struct sscop *, struct sscop_msg *); int (*cond)(struct sscop *); func = state_matrix[sscop->state][sig].func; cond = state_matrix[sscop->state][sig].cond; if(func == NULL) { VERBOSE(sscop, SSCOP_DBG_BUG, (sscop, sscop->aarg, "no handler for %s in state %s - ignored", events[sig], states[sscop->state])); SSCOP_MSG_FREE(msg); return 1; } if(cond == NULL || (*cond)(sscop)) { VERBOSE(sscop, SSCOP_DBG_EXEC, (sscop, sscop->aarg, "executing %s in %s", events[sig], states[sscop->state])); (*func)(sscop, msg); return 1; } VERBOSE(sscop, SSCOP_DBG_EXEC, (sscop, sscop->aarg, "delaying %s in %s", events[sig], states[sscop->state])); return 0; } /* * Deliver a signal to the given sscop * If it is delivered from inside a signal handler - queue it. If not, * execute it. After execution loop through the queue and execute all * pending signals. Signals, that cannot be executed because of entry * conditions are skipped. */ static void sscop_signal(struct sscop *sscop, u_int sig, struct sscop_msg *msg) { struct sscop_sig *s; VERBOSE(sscop, SSCOP_DBG_INSIG, (sscop, sscop->aarg, "got signal %s in state %s%s", events[sig], states[sscop->state], sscop->in_sig ? " -- queuing" : "")); SIG_ALLOC(s); if(s == NULL) { FAILURE("sscop: cannot allocate signal"); SSCOP_MSG_FREE(msg); return; } s->sig = sig; s->msg = msg; SIGQ_APPEND(&sscop->sigs, s); if(!sscop->in_sig) handle_sigs(sscop); } /* * Loop through the signal queue until we can't execute any signals. */ static void handle_sigs(struct sscop *sscop) { struct sscop_sig *s; sscop_sigq_head_t dsigs, q; int exec; sscop->in_sig++; /* * Copy the current signal queue to the local one and empty * the signal queue. Then loop through the signals. After one * pass we have a list of delayed signals because of entry * conditions and a new list of signals. Merge them. Repeat until * the signal queue is either empty or contains only delayed signals. */ SIGQ_INIT(&q); SIGQ_INIT(&dsigs); do { exec = 0; /* * Copy signal list and make sscop list empty */ SIGQ_MOVE(&sscop->sigs, &q); /* * Loop through the list */ while((s = SIGQ_GET(&q)) != NULL) { if(sig_exec(sscop, s->sig, s->msg)) { exec = 1; SIG_FREE(s); } else { SIGQ_APPEND(&dsigs, s); } } /* * Merge lists by inserting delayed signals in front of * the signal list. preserving the order. */ SIGQ_PREPEND(&dsigs, &sscop->sigs); } while(exec); sscop->in_sig--; } /* * Save a signal that should be executed only if state changes. */ static void sscop_save_signal(struct sscop *sscop, u_int sig, struct sscop_msg *msg) { struct sscop_sig *s; SIG_ALLOC(s); if(s == NULL) { FAILURE("sscop: cannot allocate signal"); SSCOP_MSG_FREE(msg); return; } s->sig = sig; s->msg = msg; SIGQ_APPEND(&sscop->saved_sigs, s); } /* * Set a new state. If signals are waiting for a state change - append them to * the signal queue, so they get executed. */ static void sscop_set_state(struct sscop *sscop, u_int nstate) { VERBOSE(sscop, SSCOP_DBG_STATE, (sscop, sscop->aarg, "changing state from %s to %s", states[sscop->state], states[nstate])); sscop->state = nstate; SIGQ_MOVE(&sscop->saved_sigs, &sscop->sigs); } void sscop_setdebug(struct sscop *sscop, u_int n) { sscop->debug = n; } u_int sscop_getdebug(const struct sscop *sscop) { return (sscop->debug); } Index: projects/sendfile/sys/dev/beri/virtio/virtio_mmio_platform.c =================================================================== --- projects/sendfile/sys/dev/beri/virtio/virtio_mmio_platform.c (nonexistent) +++ projects/sendfile/sys/dev/beri/virtio/virtio_mmio_platform.c (revision 274717) @@ -0,0 +1,227 @@ +/*- + * Copyright (c) 2014 Ruslan Bukin + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + */ + +/* + * BERI interface for Virtio MMIO bus. + * + * This driver provides interrupt-engine for software-implemented + * Virtio MMIO backend. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "virtio_mmio_if.h" +#include "pio_if.h" + +static void platform_intr(void *arg); + +struct virtio_mmio_platform_softc { + struct resource *res[1]; + bus_space_tag_t bst; + bus_space_handle_t bsh; + device_t dev; + void (*intr_handler)(void *); + void *ih_user; + device_t pio_recv; + device_t pio_send; +}; + +static int +setup_pio(struct virtio_mmio_platform_softc *sc, char *name, device_t *dev) +{ + phandle_t pio_node; + struct fdt_ic *ic; + phandle_t xref; + phandle_t node; + + if ((node = ofw_bus_get_node(sc->dev)) == -1) + return (ENXIO); + + if (OF_searchencprop(node, name, &xref, + sizeof(xref)) == -1) { + return (ENXIO); + } + + pio_node = OF_node_from_xref(xref); + SLIST_FOREACH(ic, &fdt_ic_list_head, fdt_ics) { + if (ic->iph == pio_node) { + *dev = ic->dev; + PIO_CONFIGURE(*dev, PIO_OUT_ALL, + PIO_UNMASK_ALL); + return (0); + } + } + + return (ENXIO); +} + +static int +virtio_mmio_platform_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (!ofw_bus_is_compatible(dev, "beri,virtio_mmio_platform")) + return (ENXIO); + + device_set_desc(dev, "Virtio MMIO platform"); + return (BUS_PROBE_DEFAULT); +} + +static int +virtio_mmio_platform_attach(device_t dev) +{ + struct virtio_mmio_platform_softc *sc; + struct fdt_ic *fic; + phandle_t node; + + sc = device_get_softc(dev); + sc->dev = dev; + + if (setup_pio(sc, "pio-send", &sc->pio_send) != 0) + return (ENXIO); + if (setup_pio(sc, "pio-recv", &sc->pio_recv) != 0) + return (ENXIO); + + if ((node = ofw_bus_get_node(sc->dev)) == -1) + return (ENXIO); + + fic = malloc(sizeof(*fic), M_DEVBUF, M_WAITOK|M_ZERO); + fic->iph = node; + fic->dev = dev; + SLIST_INSERT_HEAD(&fdt_ic_list_head, fic, fdt_ics); + + return (0); +} + +static int +platform_note(device_t dev, size_t offset) +{ + struct virtio_mmio_platform_softc *sc; + + sc = device_get_softc(dev); + + if (offset == VIRTIO_MMIO_QUEUE_NOTIFY) { + mips_dcache_wbinv_all(); + PIO_SET(sc->pio_send, Q_NOTIFY, 1); + } + + if (offset == VIRTIO_MMIO_QUEUE_PFN) { + mips_dcache_wbinv_all(); + PIO_SET(sc->pio_send, Q_PFN, 1); + } + + return (0); +} + +static void +platform_intr(void *arg) +{ + struct virtio_mmio_platform_softc *sc; + int reg; + + sc = arg; + + /* Read pending */ + reg = PIO_READ(sc->pio_recv); + + /* Ack */ + PIO_SET(sc->pio_recv, reg, 0); + + /* Writeback, invalidate cache */ + mips_dcache_wbinv_all(); + + if (sc->intr_handler != NULL) + sc->intr_handler(sc->ih_user); +} + +static int +platform_setup_intr(device_t dev, device_t mmio_dev, + void *intr_handler, void *ih_user) +{ + struct virtio_mmio_platform_softc *sc; + + sc = device_get_softc(dev); + + sc->intr_handler = intr_handler; + sc->ih_user = ih_user; + + PIO_SETUP_IRQ(sc->pio_recv, platform_intr, sc); + + return (0); +} + +static device_method_t virtio_mmio_platform_methods[] = { + DEVMETHOD(device_probe, virtio_mmio_platform_probe), + DEVMETHOD(device_attach, virtio_mmio_platform_attach), + + /* virtio_mmio_if.h */ + DEVMETHOD(virtio_mmio_note, platform_note), + DEVMETHOD(virtio_mmio_setup_intr, platform_setup_intr), + DEVMETHOD_END +}; + +static driver_t virtio_mmio_platform_driver = { + "virtio_mmio_platform", + virtio_mmio_platform_methods, + sizeof(struct virtio_mmio_platform_softc), +}; + +static devclass_t virtio_mmio_platform_devclass; + +DRIVER_MODULE(virtio_mmio_platform, simplebus, virtio_mmio_platform_driver, + virtio_mmio_platform_devclass, 0, 0); Property changes on: projects/sendfile/sys/dev/beri/virtio/virtio_mmio_platform.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: projects/sendfile/sys/dev/beri/virtio/virtio_mmio_platform.h =================================================================== --- projects/sendfile/sys/dev/beri/virtio/virtio_mmio_platform.h (nonexistent) +++ projects/sendfile/sys/dev/beri/virtio/virtio_mmio_platform.h (revision 274717) @@ -0,0 +1,35 @@ +/*- + * Copyright (c) 2014 Ruslan Bukin + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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$ + */ + +#define Q_NOTIFY 0x1 +#define Q_PFN 0x2 +#define Q_INTR 0x4 Property changes on: projects/sendfile/sys/dev/beri/virtio/virtio_mmio_platform.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: projects/sendfile/sys/dev/isp/isp_freebsd.c =================================================================== --- projects/sendfile/sys/dev/isp/isp_freebsd.c (revision 274716) +++ projects/sendfile/sys/dev/isp/isp_freebsd.c (revision 274717) @@ -1,6463 +1,6462 @@ /*- * Copyright (c) 1997-2009 by Matthew Jacob * All rights reserved. * * 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 immediately at the beginning of the file, without modification, * this list of conditions, and the following disclaimer. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * Platform (FreeBSD) dependent common attachment code for Qlogic adapters. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #if __FreeBSD_version < 800002 #define THREAD_CREATE kthread_create #else #define THREAD_CREATE kproc_create #endif MODULE_VERSION(isp, 1); MODULE_DEPEND(isp, cam, 1, 1, 1); int isp_announced = 0; int isp_fabric_hysteresis = 5; int isp_loop_down_limit = 60; /* default loop down limit */ int isp_change_is_bad = 0; /* "changed" devices are bad */ int isp_quickboot_time = 7; /* don't wait more than N secs for loop up */ int isp_gone_device_time = 30; /* grace time before reporting device lost */ int isp_autoconfig = 1; /* automatically attach/detach devices */ static const char prom3[] = "Chan %d PortID 0x%06x Departed from Target %u because of %s"; static void isp_freeze_loopdown(ispsoftc_t *, int, char *); static d_ioctl_t ispioctl; static void isp_intr_enable(void *); static void isp_cam_async(void *, uint32_t, struct cam_path *, void *); static void isp_poll(struct cam_sim *); static timeout_t isp_watchdog; static timeout_t isp_gdt; static task_fn_t isp_gdt_task; static timeout_t isp_ldt; static task_fn_t isp_ldt_task; static void isp_kthread(void *); static void isp_action(struct cam_sim *, union ccb *); #ifdef ISP_INTERNAL_TARGET static void isp_target_thread_pi(void *); static void isp_target_thread_fc(void *); #endif static int isp_timer_count; static void isp_timer(void *); static struct cdevsw isp_cdevsw = { .d_version = D_VERSION, .d_ioctl = ispioctl, .d_name = "isp", }; static int isp_attach_chan(ispsoftc_t *isp, struct cam_devq *devq, int chan) { struct ccb_setasync csa; struct cam_sim *sim; struct cam_path *path; /* * Construct our SIM entry. */ sim = cam_sim_alloc(isp_action, isp_poll, "isp", isp, device_get_unit(isp->isp_dev), &isp->isp_osinfo.lock, isp->isp_maxcmds, isp->isp_maxcmds, devq); if (sim == NULL) { return (ENOMEM); } ISP_LOCK(isp); if (xpt_bus_register(sim, isp->isp_dev, chan) != CAM_SUCCESS) { ISP_UNLOCK(isp); cam_sim_free(sim, FALSE); return (EIO); } ISP_UNLOCK(isp); if (xpt_create_path(&path, NULL, cam_sim_path(sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { ISP_LOCK(isp); xpt_bus_deregister(cam_sim_path(sim)); ISP_UNLOCK(isp); cam_sim_free(sim, FALSE); return (ENXIO); } xpt_setup_ccb(&csa.ccb_h, path, 5); csa.ccb_h.func_code = XPT_SASYNC_CB; csa.event_enable = AC_LOST_DEVICE; csa.callback = isp_cam_async; csa.callback_arg = sim; ISP_LOCK(isp); xpt_action((union ccb *)&csa); ISP_UNLOCK(isp); if (IS_SCSI(isp)) { struct isp_spi *spi = ISP_SPI_PC(isp, chan); spi->sim = sim; spi->path = path; #ifdef ISP_INTERNAL_TARGET ISP_SET_PC(isp, chan, proc_active, 1); if (THREAD_CREATE(isp_target_thread_pi, spi, &spi->target_proc, 0, 0, "%s: isp_test_tgt%d", device_get_nameunit(isp->isp_osinfo.dev), chan)) { ISP_SET_PC(isp, chan, proc_active, 0); isp_prt(isp, ISP_LOGERR, "cannot create test target thread"); } ISP_SPI_PC(isp, chan)->num_threads += 1; #endif } else { fcparam *fcp = FCPARAM(isp, chan); struct isp_fc *fc = ISP_FC_PC(isp, chan); ISP_LOCK(isp); fc->sim = sim; fc->path = path; fc->isp = isp; fc->ready = 1; callout_init_mtx(&fc->ldt, &isp->isp_osinfo.lock, 0); callout_init_mtx(&fc->gdt, &isp->isp_osinfo.lock, 0); TASK_INIT(&fc->ltask, 1, isp_ldt_task, fc); TASK_INIT(&fc->gtask, 1, isp_gdt_task, fc); /* * We start by being "loop down" if we have an initiator role */ if (fcp->role & ISP_ROLE_INITIATOR) { isp_freeze_loopdown(isp, chan, "isp_attach"); callout_reset(&fc->ldt, isp_quickboot_time * hz, isp_ldt, fc); isp_prt(isp, ISP_LOG_SANCFG|ISP_LOGDEBUG0, "Starting Initial Loop Down Timer @ %lu", (unsigned long) time_uptime); } ISP_UNLOCK(isp); if (THREAD_CREATE(isp_kthread, fc, &fc->kproc, 0, 0, "%s: fc_thrd%d", device_get_nameunit(isp->isp_osinfo.dev), chan)) { xpt_free_path(fc->path); ISP_LOCK(isp); if (callout_active(&fc->ldt)) callout_stop(&fc->ldt); xpt_bus_deregister(cam_sim_path(fc->sim)); ISP_UNLOCK(isp); cam_sim_free(fc->sim, FALSE); return (ENOMEM); } ISP_FC_PC(isp, chan)->num_threads += 1; #ifdef ISP_INTERNAL_TARGET ISP_SET_PC(isp, chan, proc_active, 1); if (THREAD_CREATE(isp_target_thread_fc, fc, &fc->target_proc, 0, 0, "%s: isp_test_tgt%d", device_get_nameunit(isp->isp_osinfo.dev), chan)) { ISP_SET_PC(isp, chan, proc_active, 0); isp_prt(isp, ISP_LOGERR, "cannot create test target thread"); } ISP_FC_PC(isp, chan)->num_threads += 1; #endif if (chan == 0) { struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(isp->isp_osinfo.dev); struct sysctl_oid *tree = device_get_sysctl_tree(isp->isp_osinfo.dev); SYSCTL_ADD_QUAD(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "wwnn", CTLFLAG_RD, &FCPARAM(isp, 0)->isp_wwnn, "World Wide Node Name"); SYSCTL_ADD_QUAD(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "wwpn", CTLFLAG_RD, &FCPARAM(isp, 0)->isp_wwpn, "World Wide Port Name"); SYSCTL_ADD_UINT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "loop_down_limit", CTLFLAG_RW, &ISP_FC_PC(isp, 0)->loop_down_limit, 0, "Loop Down Limit"); SYSCTL_ADD_UINT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "gone_device_time", CTLFLAG_RW, &ISP_FC_PC(isp, 0)->gone_device_time, 0, "Gone Device Time"); #if defined(ISP_TARGET_MODE) && defined(DEBUG) SYSCTL_ADD_UINT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "inject_lost_data_frame", CTLFLAG_RW, &ISP_FC_PC(isp, 0)->inject_lost_data_frame, 0, "Cause a Lost Frame on a Read"); #endif } } return (0); } static void isp_detach_internal_target(ispsoftc_t *isp, int chan) { #ifdef ISP_INTERNAL_TARGET void *wchan; ISP_GET_PC(isp, chan, target_proc, wchan); ISP_SET_PC(isp, chan, proc_active, 0); wakeup(wchan); #endif } static void isp_detach_chan(ispsoftc_t *isp, int chan) { struct cam_sim *sim; struct cam_path *path; struct ccb_setasync csa; int *num_threads; ISP_GET_PC(isp, chan, sim, sim); ISP_GET_PC(isp, chan, path, path); ISP_GET_PC_ADDR(isp, chan, num_threads, num_threads); xpt_setup_ccb(&csa.ccb_h, path, 5); csa.ccb_h.func_code = XPT_SASYNC_CB; csa.event_enable = 0; csa.callback = isp_cam_async; csa.callback_arg = sim; xpt_action((union ccb *)&csa); xpt_free_path(path); xpt_bus_deregister(cam_sim_path(sim)); cam_sim_free(sim, FALSE); /* Wait for the channel's spawned threads to exit. */ wakeup(isp->isp_osinfo.pc.ptr); isp_detach_internal_target(isp, chan); while (*num_threads != 0) mtx_sleep(isp, &isp->isp_osinfo.lock, PRIBIO, "isp_reap", 100); } int isp_attach(ispsoftc_t *isp) { const char *nu = device_get_nameunit(isp->isp_osinfo.dev); int du = device_get_unit(isp->isp_dev); int chan; isp->isp_osinfo.ehook.ich_func = isp_intr_enable; isp->isp_osinfo.ehook.ich_arg = isp; /* * Haha. Set this first, because if we're loaded as a module isp_intr_enable * will be called right awawy, which will clear isp_osinfo.ehook_active, * which would be unwise to then set again later. */ isp->isp_osinfo.ehook_active = 1; if (config_intrhook_establish(&isp->isp_osinfo.ehook) != 0) { isp_prt(isp, ISP_LOGERR, "could not establish interrupt enable hook"); return (-EIO); } /* * Create the device queue for our SIM(s). */ isp->isp_osinfo.devq = cam_simq_alloc(isp->isp_maxcmds); if (isp->isp_osinfo.devq == NULL) { config_intrhook_disestablish(&isp->isp_osinfo.ehook); return (EIO); } for (chan = 0; chan < isp->isp_nchan; chan++) { if (isp_attach_chan(isp, isp->isp_osinfo.devq, chan)) { goto unwind; } } callout_init_mtx(&isp->isp_osinfo.tmo, &isp->isp_osinfo.lock, 0); isp_timer_count = hz >> 2; callout_reset(&isp->isp_osinfo.tmo, isp_timer_count, isp_timer, isp); isp->isp_osinfo.timer_active = 1; isp->isp_osinfo.cdev = make_dev(&isp_cdevsw, du, UID_ROOT, GID_OPERATOR, 0600, "%s", nu); if (isp->isp_osinfo.cdev) { isp->isp_osinfo.cdev->si_drv1 = isp; } return (0); unwind: while (--chan >= 0) { struct cam_sim *sim; struct cam_path *path; ISP_GET_PC(isp, chan, sim, sim); ISP_GET_PC(isp, chan, path, path); xpt_free_path(path); ISP_LOCK(isp); xpt_bus_deregister(cam_sim_path(sim)); ISP_UNLOCK(isp); cam_sim_free(sim, FALSE); } if (isp->isp_osinfo.ehook_active) { config_intrhook_disestablish(&isp->isp_osinfo.ehook); isp->isp_osinfo.ehook_active = 0; } if (isp->isp_osinfo.cdev) { destroy_dev(isp->isp_osinfo.cdev); isp->isp_osinfo.cdev = NULL; } cam_simq_free(isp->isp_osinfo.devq); isp->isp_osinfo.devq = NULL; return (-1); } int isp_detach(ispsoftc_t *isp) { struct cam_sim *sim; int chan; ISP_LOCK(isp); for (chan = isp->isp_nchan - 1; chan >= 0; chan -= 1) { ISP_GET_PC(isp, chan, sim, sim); if (sim->refcount > 2) { ISP_UNLOCK(isp); return (EBUSY); } } /* Tell spawned threads that we're exiting. */ isp->isp_osinfo.is_exiting = 1; if (isp->isp_osinfo.timer_active) { callout_stop(&isp->isp_osinfo.tmo); isp->isp_osinfo.timer_active = 0; } for (chan = isp->isp_nchan - 1; chan >= 0; chan -= 1) isp_detach_chan(isp, chan); ISP_UNLOCK(isp); if (isp->isp_osinfo.cdev) { destroy_dev(isp->isp_osinfo.cdev); isp->isp_osinfo.cdev = NULL; } if (isp->isp_osinfo.ehook_active) { config_intrhook_disestablish(&isp->isp_osinfo.ehook); isp->isp_osinfo.ehook_active = 0; } if (isp->isp_osinfo.devq != NULL) { cam_simq_free(isp->isp_osinfo.devq); isp->isp_osinfo.devq = NULL; } return (0); } static void isp_freeze_loopdown(ispsoftc_t *isp, int chan, char *msg) { if (IS_FC(isp)) { struct isp_fc *fc = ISP_FC_PC(isp, chan); if (fc->simqfrozen == 0) { isp_prt(isp, ISP_LOGDEBUG0, "%s: freeze simq (loopdown) chan %d", msg, chan); fc->simqfrozen = SIMQFRZ_LOOPDOWN; xpt_freeze_simq(fc->sim, 1); } else { isp_prt(isp, ISP_LOGDEBUG0, "%s: mark frozen (loopdown) chan %d", msg, chan); fc->simqfrozen |= SIMQFRZ_LOOPDOWN; } } } static void isp_unfreeze_loopdown(ispsoftc_t *isp, int chan) { if (IS_FC(isp)) { struct isp_fc *fc = ISP_FC_PC(isp, chan); int wasfrozen = fc->simqfrozen & SIMQFRZ_LOOPDOWN; fc->simqfrozen &= ~SIMQFRZ_LOOPDOWN; if (wasfrozen && fc->simqfrozen == 0) { isp_prt(isp, ISP_LOG_SANCFG|ISP_LOGDEBUG0, "%s: Chan %d releasing simq", __func__, chan); xpt_release_simq(fc->sim, 1); } } } static int ispioctl(struct cdev *dev, u_long c, caddr_t addr, int flags, struct thread *td) { ispsoftc_t *isp; int nr, chan, retval = ENOTTY; isp = dev->si_drv1; switch (c) { case ISP_SDBLEV: { int olddblev = isp->isp_dblev; isp->isp_dblev = *(int *)addr; *(int *)addr = olddblev; retval = 0; break; } case ISP_GETROLE: chan = *(int *)addr; if (chan < 0 || chan >= isp->isp_nchan) { retval = -ENXIO; break; } if (IS_FC(isp)) { *(int *)addr = FCPARAM(isp, chan)->role; } else { *(int *)addr = SDPARAM(isp, chan)->role; } retval = 0; break; case ISP_SETROLE: nr = *(int *)addr; chan = nr >> 8; if (chan < 0 || chan >= isp->isp_nchan) { retval = -ENXIO; break; } nr &= 0xff; if (nr & ~(ISP_ROLE_INITIATOR|ISP_ROLE_TARGET)) { retval = EINVAL; break; } if (IS_FC(isp)) { /* * We don't really support dual role at present on FC cards. * * We should, but a bunch of things are currently broken, * so don't allow it. */ if (nr == ISP_ROLE_BOTH) { isp_prt(isp, ISP_LOGERR, "cannot support dual role at present"); retval = EINVAL; break; } *(int *)addr = FCPARAM(isp, chan)->role; #ifdef ISP_INTERNAL_TARGET ISP_LOCK(isp); retval = isp_fc_change_role(isp, chan, nr); ISP_UNLOCK(isp); #else FCPARAM(isp, chan)->role = nr; #endif } else { *(int *)addr = SDPARAM(isp, chan)->role; SDPARAM(isp, chan)->role = nr; } retval = 0; break; case ISP_RESETHBA: ISP_LOCK(isp); #ifdef ISP_TARGET_MODE isp_del_all_wwn_entries(isp, ISP_NOCHAN); #endif isp_reinit(isp, 0); ISP_UNLOCK(isp); retval = 0; break; case ISP_RESCAN: if (IS_FC(isp)) { chan = *(int *)addr; if (chan < 0 || chan >= isp->isp_nchan) { retval = -ENXIO; break; } ISP_LOCK(isp); if (isp_fc_runstate(isp, chan, 5 * 1000000)) { retval = EIO; } else { retval = 0; } ISP_UNLOCK(isp); } break; case ISP_FC_LIP: if (IS_FC(isp)) { chan = *(int *)addr; if (chan < 0 || chan >= isp->isp_nchan) { retval = -ENXIO; break; } ISP_LOCK(isp); if (isp_control(isp, ISPCTL_SEND_LIP, chan)) { retval = EIO; } else { retval = 0; } ISP_UNLOCK(isp); } break; case ISP_FC_GETDINFO: { struct isp_fc_device *ifc = (struct isp_fc_device *) addr; fcportdb_t *lp; if (IS_SCSI(isp)) { break; } if (ifc->loopid >= MAX_FC_TARG) { retval = EINVAL; break; } lp = &FCPARAM(isp, ifc->chan)->portdb[ifc->loopid]; if (lp->state == FC_PORTDB_STATE_VALID || lp->target_mode) { ifc->role = (lp->prli_word3 & SVC3_ROLE_MASK) >> SVC3_ROLE_SHIFT; ifc->loopid = lp->handle; ifc->portid = lp->portid; ifc->node_wwn = lp->node_wwn; ifc->port_wwn = lp->port_wwn; retval = 0; } else { retval = ENODEV; } break; } case ISP_GET_STATS: { isp_stats_t *sp = (isp_stats_t *) addr; ISP_MEMZERO(sp, sizeof (*sp)); sp->isp_stat_version = ISP_STATS_VERSION; sp->isp_type = isp->isp_type; sp->isp_revision = isp->isp_revision; ISP_LOCK(isp); sp->isp_stats[ISP_INTCNT] = isp->isp_intcnt; sp->isp_stats[ISP_INTBOGUS] = isp->isp_intbogus; sp->isp_stats[ISP_INTMBOXC] = isp->isp_intmboxc; sp->isp_stats[ISP_INGOASYNC] = isp->isp_intoasync; sp->isp_stats[ISP_RSLTCCMPLT] = isp->isp_rsltccmplt; sp->isp_stats[ISP_FPHCCMCPLT] = isp->isp_fphccmplt; sp->isp_stats[ISP_RSCCHIWAT] = isp->isp_rscchiwater; sp->isp_stats[ISP_FPCCHIWAT] = isp->isp_fpcchiwater; ISP_UNLOCK(isp); retval = 0; break; } case ISP_CLR_STATS: ISP_LOCK(isp); isp->isp_intcnt = 0; isp->isp_intbogus = 0; isp->isp_intmboxc = 0; isp->isp_intoasync = 0; isp->isp_rsltccmplt = 0; isp->isp_fphccmplt = 0; isp->isp_rscchiwater = 0; isp->isp_fpcchiwater = 0; ISP_UNLOCK(isp); retval = 0; break; case ISP_FC_GETHINFO: { struct isp_hba_device *hba = (struct isp_hba_device *) addr; int chan = hba->fc_channel; if (chan < 0 || chan >= isp->isp_nchan) { retval = ENXIO; break; } hba->fc_fw_major = ISP_FW_MAJORX(isp->isp_fwrev); hba->fc_fw_minor = ISP_FW_MINORX(isp->isp_fwrev); hba->fc_fw_micro = ISP_FW_MICROX(isp->isp_fwrev); hba->fc_nchannels = isp->isp_nchan; if (IS_FC(isp)) { hba->fc_nports = MAX_FC_TARG; hba->fc_speed = FCPARAM(isp, hba->fc_channel)->isp_gbspeed; hba->fc_topology = FCPARAM(isp, chan)->isp_topo + 1; hba->fc_loopid = FCPARAM(isp, chan)->isp_loopid; hba->nvram_node_wwn = FCPARAM(isp, chan)->isp_wwnn_nvram; hba->nvram_port_wwn = FCPARAM(isp, chan)->isp_wwpn_nvram; hba->active_node_wwn = FCPARAM(isp, chan)->isp_wwnn; hba->active_port_wwn = FCPARAM(isp, chan)->isp_wwpn; } else { hba->fc_nports = MAX_TARGETS; hba->fc_speed = 0; hba->fc_topology = 0; hba->nvram_node_wwn = 0ull; hba->nvram_port_wwn = 0ull; hba->active_node_wwn = 0ull; hba->active_port_wwn = 0ull; } retval = 0; break; } case ISP_TSK_MGMT: { int needmarker; struct isp_fc_tsk_mgmt *fct = (struct isp_fc_tsk_mgmt *) addr; uint16_t loopid; mbreg_t mbs; if (IS_SCSI(isp)) { break; } chan = fct->chan; if (chan < 0 || chan >= isp->isp_nchan) { retval = -ENXIO; break; } needmarker = retval = 0; loopid = fct->loopid; ISP_LOCK(isp); if (IS_24XX(isp)) { uint8_t local[QENTRY_LEN]; isp24xx_tmf_t *tmf; isp24xx_statusreq_t *sp; fcparam *fcp = FCPARAM(isp, chan); fcportdb_t *lp; int i; for (i = 0; i < MAX_FC_TARG; i++) { lp = &fcp->portdb[i]; if (lp->handle == loopid) { break; } } if (i == MAX_FC_TARG) { retval = ENXIO; ISP_UNLOCK(isp); break; } /* XXX VALIDATE LP XXX */ tmf = (isp24xx_tmf_t *) local; ISP_MEMZERO(tmf, QENTRY_LEN); tmf->tmf_header.rqs_entry_type = RQSTYPE_TSK_MGMT; tmf->tmf_header.rqs_entry_count = 1; tmf->tmf_nphdl = lp->handle; tmf->tmf_delay = 2; tmf->tmf_timeout = 2; tmf->tmf_tidlo = lp->portid; tmf->tmf_tidhi = lp->portid >> 16; tmf->tmf_vpidx = ISP_GET_VPIDX(isp, chan); tmf->tmf_lun[1] = fct->lun & 0xff; if (fct->lun >= 256) { tmf->tmf_lun[0] = 0x40 | (fct->lun >> 8); } switch (fct->action) { case IPT_CLEAR_ACA: tmf->tmf_flags = ISP24XX_TMF_CLEAR_ACA; break; case IPT_TARGET_RESET: tmf->tmf_flags = ISP24XX_TMF_TARGET_RESET; needmarker = 1; break; case IPT_LUN_RESET: tmf->tmf_flags = ISP24XX_TMF_LUN_RESET; needmarker = 1; break; case IPT_CLEAR_TASK_SET: tmf->tmf_flags = ISP24XX_TMF_CLEAR_TASK_SET; needmarker = 1; break; case IPT_ABORT_TASK_SET: tmf->tmf_flags = ISP24XX_TMF_ABORT_TASK_SET; needmarker = 1; break; default: retval = EINVAL; break; } if (retval) { ISP_UNLOCK(isp); break; } MBSINIT(&mbs, MBOX_EXEC_COMMAND_IOCB_A64, MBLOGALL, 5000000); mbs.param[1] = QENTRY_LEN; mbs.param[2] = DMA_WD1(fcp->isp_scdma); mbs.param[3] = DMA_WD0(fcp->isp_scdma); mbs.param[6] = DMA_WD3(fcp->isp_scdma); mbs.param[7] = DMA_WD2(fcp->isp_scdma); if (FC_SCRATCH_ACQUIRE(isp, chan)) { ISP_UNLOCK(isp); retval = ENOMEM; break; } isp_put_24xx_tmf(isp, tmf, fcp->isp_scratch); MEMORYBARRIER(isp, SYNC_SFORDEV, 0, QENTRY_LEN, chan); sp = (isp24xx_statusreq_t *) local; sp->req_completion_status = 1; retval = isp_control(isp, ISPCTL_RUN_MBOXCMD, &mbs); MEMORYBARRIER(isp, SYNC_SFORCPU, QENTRY_LEN, QENTRY_LEN, chan); isp_get_24xx_response(isp, &((isp24xx_statusreq_t *)fcp->isp_scratch)[1], sp); FC_SCRATCH_RELEASE(isp, chan); if (retval || sp->req_completion_status != 0) { FC_SCRATCH_RELEASE(isp, chan); retval = EIO; } if (retval == 0) { if (needmarker) { fcp->sendmarker = 1; } } } else { MBSINIT(&mbs, 0, MBLOGALL, 0); if (ISP_CAP_2KLOGIN(isp) == 0) { loopid <<= 8; } switch (fct->action) { case IPT_CLEAR_ACA: mbs.param[0] = MBOX_CLEAR_ACA; mbs.param[1] = loopid; mbs.param[2] = fct->lun; break; case IPT_TARGET_RESET: mbs.param[0] = MBOX_TARGET_RESET; mbs.param[1] = loopid; needmarker = 1; break; case IPT_LUN_RESET: mbs.param[0] = MBOX_LUN_RESET; mbs.param[1] = loopid; mbs.param[2] = fct->lun; needmarker = 1; break; case IPT_CLEAR_TASK_SET: mbs.param[0] = MBOX_CLEAR_TASK_SET; mbs.param[1] = loopid; mbs.param[2] = fct->lun; needmarker = 1; break; case IPT_ABORT_TASK_SET: mbs.param[0] = MBOX_ABORT_TASK_SET; mbs.param[1] = loopid; mbs.param[2] = fct->lun; needmarker = 1; break; default: retval = EINVAL; break; } if (retval == 0) { if (needmarker) { FCPARAM(isp, chan)->sendmarker = 1; } retval = isp_control(isp, ISPCTL_RUN_MBOXCMD, &mbs); if (retval) { retval = EIO; } } } ISP_UNLOCK(isp); break; } default: break; } return (retval); } static void isp_intr_enable(void *arg) { int chan; ispsoftc_t *isp = arg; ISP_LOCK(isp); for (chan = 0; chan < isp->isp_nchan; chan++) { if (IS_FC(isp)) { if (FCPARAM(isp, chan)->role != ISP_ROLE_NONE) { ISP_ENABLE_INTS(isp); break; } } else { if (SDPARAM(isp, chan)->role != ISP_ROLE_NONE) { ISP_ENABLE_INTS(isp); break; } } } isp->isp_osinfo.ehook_active = 0; ISP_UNLOCK(isp); /* Release our hook so that the boot can continue. */ config_intrhook_disestablish(&isp->isp_osinfo.ehook); } /* * Local Inlines */ static ISP_INLINE int isp_get_pcmd(ispsoftc_t *, union ccb *); static ISP_INLINE void isp_free_pcmd(ispsoftc_t *, union ccb *); static ISP_INLINE int isp_get_pcmd(ispsoftc_t *isp, union ccb *ccb) { ISP_PCMD(ccb) = isp->isp_osinfo.pcmd_free; if (ISP_PCMD(ccb) == NULL) { return (-1); } isp->isp_osinfo.pcmd_free = ((struct isp_pcmd *)ISP_PCMD(ccb))->next; return (0); } static ISP_INLINE void isp_free_pcmd(ispsoftc_t *isp, union ccb *ccb) { if (ISP_PCMD(ccb)) { #ifdef ISP_TARGET_MODE PISP_PCMD(ccb)->datalen = 0; PISP_PCMD(ccb)->totslen = 0; PISP_PCMD(ccb)->cumslen = 0; PISP_PCMD(ccb)->crn = 0; #endif PISP_PCMD(ccb)->next = isp->isp_osinfo.pcmd_free; isp->isp_osinfo.pcmd_free = ISP_PCMD(ccb); ISP_PCMD(ccb) = NULL; } } /* * Put the target mode functions here, because some are inlines */ #ifdef ISP_TARGET_MODE static ISP_INLINE void isp_tmlock(ispsoftc_t *, const char *); static ISP_INLINE void isp_tmunlk(ispsoftc_t *); static ISP_INLINE int is_any_lun_enabled(ispsoftc_t *, int); static ISP_INLINE int is_lun_enabled(ispsoftc_t *, int, lun_id_t); static ISP_INLINE tstate_t *get_lun_statep(ispsoftc_t *, int, lun_id_t); static ISP_INLINE tstate_t *get_lun_statep_from_tag(ispsoftc_t *, int, uint32_t); static ISP_INLINE void rls_lun_statep(ispsoftc_t *, tstate_t *); static ISP_INLINE inot_private_data_t *get_ntp_from_tagdata(ispsoftc_t *, uint32_t, uint32_t, tstate_t **); static ISP_INLINE atio_private_data_t *isp_get_atpd(ispsoftc_t *, tstate_t *, uint32_t); static ISP_INLINE atio_private_data_t *isp_find_atpd(ispsoftc_t *, tstate_t *, uint32_t); static ISP_INLINE void isp_put_atpd(ispsoftc_t *, tstate_t *, atio_private_data_t *); static ISP_INLINE inot_private_data_t *isp_get_ntpd(ispsoftc_t *, tstate_t *); static ISP_INLINE inot_private_data_t *isp_find_ntpd(ispsoftc_t *, tstate_t *, uint32_t, uint32_t); static ISP_INLINE void isp_put_ntpd(ispsoftc_t *, tstate_t *, inot_private_data_t *); static cam_status create_lun_state(ispsoftc_t *, int, struct cam_path *, tstate_t **); static void destroy_lun_state(ispsoftc_t *, tstate_t *); static void isp_enable_lun(ispsoftc_t *, union ccb *); static cam_status isp_enable_deferred_luns(ispsoftc_t *, int); static cam_status isp_enable_deferred(ispsoftc_t *, int, lun_id_t); static void isp_disable_lun(ispsoftc_t *, union ccb *); static int isp_enable_target_mode(ispsoftc_t *, int); static int isp_disable_target_mode(ispsoftc_t *, int); static void isp_ledone(ispsoftc_t *, lun_entry_t *); static timeout_t isp_refire_putback_atio; static timeout_t isp_refire_notify_ack; static void isp_complete_ctio(union ccb *); static void isp_target_putback_atio(union ccb *); enum Start_Ctio_How { FROM_CAM, FROM_TIMER, FROM_SRR, FROM_CTIO_DONE }; static void isp_target_start_ctio(ispsoftc_t *, union ccb *, enum Start_Ctio_How); static void isp_handle_platform_atio(ispsoftc_t *, at_entry_t *); static void isp_handle_platform_atio2(ispsoftc_t *, at2_entry_t *); static void isp_handle_platform_atio7(ispsoftc_t *, at7_entry_t *); static void isp_handle_platform_ctio(ispsoftc_t *, void *); static void isp_handle_platform_notify_scsi(ispsoftc_t *, in_entry_t *); static void isp_handle_platform_notify_fc(ispsoftc_t *, in_fcentry_t *); static void isp_handle_platform_notify_24xx(ispsoftc_t *, in_fcentry_24xx_t *); static int isp_handle_platform_target_notify_ack(ispsoftc_t *, isp_notify_t *); static void isp_handle_platform_target_tmf(ispsoftc_t *, isp_notify_t *); static void isp_target_mark_aborted(ispsoftc_t *, union ccb *); static void isp_target_mark_aborted_early(ispsoftc_t *, tstate_t *, uint32_t); static ISP_INLINE void isp_tmlock(ispsoftc_t *isp, const char *msg) { while (isp->isp_osinfo.tmbusy) { isp->isp_osinfo.tmwanted = 1; mtx_sleep(isp, &isp->isp_lock, PRIBIO, msg, 0); } isp->isp_osinfo.tmbusy = 1; } static ISP_INLINE void isp_tmunlk(ispsoftc_t *isp) { isp->isp_osinfo.tmbusy = 0; if (isp->isp_osinfo.tmwanted) { isp->isp_osinfo.tmwanted = 0; wakeup(isp); } } static ISP_INLINE int is_any_lun_enabled(ispsoftc_t *isp, int bus) { struct tslist *lhp; int i; for (i = 0; i < LUN_HASH_SIZE; i++) { ISP_GET_PC_ADDR(isp, bus, lun_hash[i], lhp); if (SLIST_FIRST(lhp)) return (1); } return (0); } static ISP_INLINE int is_lun_enabled(ispsoftc_t *isp, int bus, lun_id_t lun) { tstate_t *tptr; struct tslist *lhp; ISP_GET_PC_ADDR(isp, bus, lun_hash[LUN_HASH_FUNC(lun)], lhp); SLIST_FOREACH(tptr, lhp, next) { if (xpt_path_lun_id(tptr->owner) == lun) { return (1); } } return (0); } static void dump_tstates(ispsoftc_t *isp, int bus) { int i, j; struct tslist *lhp; tstate_t *tptr = NULL; if (bus >= isp->isp_nchan) { return; } for (i = 0; i < LUN_HASH_SIZE; i++) { ISP_GET_PC_ADDR(isp, bus, lun_hash[i], lhp); j = 0; SLIST_FOREACH(tptr, lhp, next) { xpt_print(tptr->owner, "[%d, %d] atio_cnt=%d inot_cnt=%d\n", i, j, tptr->atio_count, tptr->inot_count); j++; } } } static ISP_INLINE tstate_t * get_lun_statep(ispsoftc_t *isp, int bus, lun_id_t lun) { tstate_t *tptr = NULL; struct tslist *lhp; int i; if (bus < isp->isp_nchan) { for (i = 0; i < LUN_HASH_SIZE; i++) { ISP_GET_PC_ADDR(isp, bus, lun_hash[i], lhp); SLIST_FOREACH(tptr, lhp, next) { if (xpt_path_lun_id(tptr->owner) == lun) { tptr->hold++; return (tptr); } } } } return (NULL); } static ISP_INLINE tstate_t * get_lun_statep_from_tag(ispsoftc_t *isp, int bus, uint32_t tagval) { tstate_t *tptr = NULL; atio_private_data_t *atp; struct tslist *lhp; int i; if (bus < isp->isp_nchan && tagval != 0) { for (i = 0; i < LUN_HASH_SIZE; i++) { ISP_GET_PC_ADDR(isp, bus, lun_hash[i], lhp); SLIST_FOREACH(tptr, lhp, next) { atp = isp_find_atpd(isp, tptr, tagval); if (atp) { tptr->hold++; return (tptr); } } } } return (NULL); } static ISP_INLINE inot_private_data_t * get_ntp_from_tagdata(ispsoftc_t *isp, uint32_t tag_id, uint32_t seq_id, tstate_t **rslt) { inot_private_data_t *ntp; tstate_t *tptr; struct tslist *lhp; int bus, i; for (bus = 0; bus < isp->isp_nchan; bus++) { for (i = 0; i < LUN_HASH_SIZE; i++) { ISP_GET_PC_ADDR(isp, bus, lun_hash[i], lhp); SLIST_FOREACH(tptr, lhp, next) { ntp = isp_find_ntpd(isp, tptr, tag_id, seq_id); if (ntp) { *rslt = tptr; tptr->hold++; return (ntp); } } } } return (NULL); } static ISP_INLINE void rls_lun_statep(ispsoftc_t *isp, tstate_t *tptr) { KASSERT((tptr->hold), ("tptr not held")); tptr->hold--; } static void isp_tmcmd_restart(ispsoftc_t *isp) { inot_private_data_t *ntp; inot_private_data_t *restart_queue; tstate_t *tptr; union ccb *ccb; struct tslist *lhp; int bus, i; for (bus = 0; bus < isp->isp_nchan; bus++) { for (i = 0; i < LUN_HASH_SIZE; i++) { ISP_GET_PC_ADDR(isp, bus, lun_hash[i], lhp); SLIST_FOREACH(tptr, lhp, next) { if ((restart_queue = tptr->restart_queue) != NULL) tptr->restart_queue = NULL; while (restart_queue) { ntp = restart_queue; restart_queue = ntp->rd.nt.nt_hba; if (IS_24XX(isp)) { isp_prt(isp, ISP_LOGTDEBUG0, "%s: restarting resrc deprived %x", __func__, ((at7_entry_t *)ntp->rd.data)->at_rxid); isp_handle_platform_atio7(isp, (at7_entry_t *) ntp->rd.data); } else { isp_prt(isp, ISP_LOGTDEBUG0, "%s: restarting resrc deprived %x", __func__, ((at2_entry_t *)ntp->rd.data)->at_rxid); isp_handle_platform_atio2(isp, (at2_entry_t *) ntp->rd.data); } isp_put_ntpd(isp, tptr, ntp); if (tptr->restart_queue && restart_queue != NULL) { ntp = tptr->restart_queue; tptr->restart_queue = restart_queue; while (restart_queue->rd.nt.nt_hba) { restart_queue = restart_queue->rd.nt.nt_hba; } restart_queue->rd.nt.nt_hba = ntp; break; } } /* * We only need to do this once per tptr */ if (!TAILQ_EMPTY(&tptr->waitq)) { ccb = (union ccb *)TAILQ_LAST(&tptr->waitq, isp_ccbq); TAILQ_REMOVE(&tptr->waitq, &ccb->ccb_h, periph_links.tqe); isp_target_start_ctio(isp, ccb, FROM_TIMER); } } } } } static ISP_INLINE atio_private_data_t * isp_get_atpd(ispsoftc_t *isp, tstate_t *tptr, uint32_t tag) { atio_private_data_t *atp; atp = LIST_FIRST(&tptr->atfree); if (atp) { LIST_REMOVE(atp, next); atp->tag = tag; LIST_INSERT_HEAD(&tptr->atused[ATPDPHASH(tag)], atp, next); } return (atp); } static ISP_INLINE atio_private_data_t * isp_find_atpd(ispsoftc_t *isp, tstate_t *tptr, uint32_t tag) { atio_private_data_t *atp; LIST_FOREACH(atp, &tptr->atused[ATPDPHASH(tag)], next) { if (atp->tag == tag) return (atp); } return (NULL); } static ISP_INLINE void isp_put_atpd(ispsoftc_t *isp, tstate_t *tptr, atio_private_data_t *atp) { if (atp->ests) { isp_put_ecmd(isp, atp->ests); } LIST_REMOVE(atp, next); memset(atp, 0, sizeof (*atp)); LIST_INSERT_HEAD(&tptr->atfree, atp, next); } static void isp_dump_atpd(ispsoftc_t *isp, tstate_t *tptr) { atio_private_data_t *atp; const char *states[8] = { "Free", "ATIO", "CAM", "CTIO", "LAST_CTIO", "PDON", "?6", "7" }; for (atp = tptr->atpool; atp < &tptr->atpool[ATPDPSIZE]; atp++) { xpt_print(tptr->owner, "ATP: [0x%x] origdlen %u bytes_xfrd %u lun %u nphdl 0x%04x s_id 0x%06x d_id 0x%06x oxid 0x%04x state %s\n", atp->tag, atp->orig_datalen, atp->bytes_xfered, atp->lun, atp->nphdl, atp->sid, atp->portid, atp->oxid, states[atp->state & 0x7]); } } static ISP_INLINE inot_private_data_t * isp_get_ntpd(ispsoftc_t *isp, tstate_t *tptr) { inot_private_data_t *ntp; ntp = tptr->ntfree; if (ntp) { tptr->ntfree = ntp->next; } return (ntp); } static ISP_INLINE inot_private_data_t * isp_find_ntpd(ispsoftc_t *isp, tstate_t *tptr, uint32_t tag_id, uint32_t seq_id) { inot_private_data_t *ntp; for (ntp = tptr->ntpool; ntp < &tptr->ntpool[ATPDPSIZE]; ntp++) { if (ntp->rd.tag_id == tag_id && ntp->rd.seq_id == seq_id) { return (ntp); } } return (NULL); } static ISP_INLINE void isp_put_ntpd(ispsoftc_t *isp, tstate_t *tptr, inot_private_data_t *ntp) { ntp->rd.tag_id = ntp->rd.seq_id = 0; ntp->next = tptr->ntfree; tptr->ntfree = ntp; } static cam_status create_lun_state(ispsoftc_t *isp, int bus, struct cam_path *path, tstate_t **rslt) { cam_status status; lun_id_t lun; struct tslist *lhp; tstate_t *tptr; int i; lun = xpt_path_lun_id(path); if (lun != CAM_LUN_WILDCARD) { if (lun >= ISP_MAX_LUNS(isp)) { return (CAM_LUN_INVALID); } } if (is_lun_enabled(isp, bus, lun)) { return (CAM_LUN_ALRDY_ENA); } tptr = malloc(sizeof (tstate_t), M_DEVBUF, M_NOWAIT|M_ZERO); if (tptr == NULL) { return (CAM_RESRC_UNAVAIL); } status = xpt_create_path(&tptr->owner, NULL, xpt_path_path_id(path), xpt_path_target_id(path), lun); if (status != CAM_REQ_CMP) { free(tptr, M_DEVBUF); return (status); } SLIST_INIT(&tptr->atios); SLIST_INIT(&tptr->inots); TAILQ_INIT(&tptr->waitq); LIST_INIT(&tptr->atfree); for (i = ATPDPSIZE-1; i >= 0; i--) LIST_INSERT_HEAD(&tptr->atfree, &tptr->atpool[i], next); for (i = 0; i < ATPDPHASHSIZE; i++) LIST_INIT(&tptr->atused[i]); for (i = 0; i < ATPDPSIZE-1; i++) tptr->ntpool[i].next = &tptr->ntpool[i+1]; tptr->ntfree = tptr->ntpool; tptr->hold = 1; ISP_GET_PC_ADDR(isp, bus, lun_hash[LUN_HASH_FUNC(xpt_path_lun_id(tptr->owner))], lhp); SLIST_INSERT_HEAD(lhp, tptr, next); *rslt = tptr; ISP_PATH_PRT(isp, ISP_LOGTDEBUG0, path, "created tstate\n"); return (CAM_REQ_CMP); } static ISP_INLINE void destroy_lun_state(ispsoftc_t *isp, tstate_t *tptr) { union ccb *ccb; struct tslist *lhp; KASSERT((tptr->hold != 0), ("tptr is not held")); KASSERT((tptr->hold == 1), ("tptr still held (%d)", tptr->hold)); do { ccb = (union ccb *)SLIST_FIRST(&tptr->atios); if (ccb) { SLIST_REMOVE_HEAD(&tptr->atios, sim_links.sle); ccb->ccb_h.status = CAM_REQ_ABORTED; xpt_done(ccb); } } while (ccb); do { ccb = (union ccb *)SLIST_FIRST(&tptr->inots); if (ccb) { SLIST_REMOVE_HEAD(&tptr->inots, sim_links.sle); ccb->ccb_h.status = CAM_REQ_ABORTED; xpt_done(ccb); } } while (ccb); ISP_GET_PC_ADDR(isp, cam_sim_bus(xpt_path_sim(tptr->owner)), lun_hash[LUN_HASH_FUNC(xpt_path_lun_id(tptr->owner))], lhp); SLIST_REMOVE(lhp, tptr, tstate, next); ISP_PATH_PRT(isp, ISP_LOGTDEBUG0, tptr->owner, "destroyed tstate\n"); xpt_free_path(tptr->owner); free(tptr, M_DEVBUF); } /* * Enable a lun. */ static void isp_enable_lun(ispsoftc_t *isp, union ccb *ccb) { tstate_t *tptr = NULL; int bus, tm_enabled, target_role; target_id_t target; lun_id_t lun; /* * We only support either a wildcard target/lun or a target ID of zero and a non-wildcard lun */ bus = XS_CHANNEL(ccb); target = ccb->ccb_h.target_id; lun = ccb->ccb_h.target_lun; ISP_PATH_PRT(isp, ISP_LOGTDEBUG0|ISP_LOGCONFIG, ccb->ccb_h.path, "enabling lun %u\n", lun); if (target != CAM_TARGET_WILDCARD && target != 0) { ccb->ccb_h.status = CAM_TID_INVALID; xpt_done(ccb); return; } if (target == CAM_TARGET_WILDCARD && lun != CAM_LUN_WILDCARD) { ccb->ccb_h.status = CAM_LUN_INVALID; xpt_done(ccb); return; } if (target != CAM_TARGET_WILDCARD && lun == CAM_LUN_WILDCARD) { ccb->ccb_h.status = CAM_LUN_INVALID; xpt_done(ccb); return; } if (isp->isp_dblev & ISP_LOGTDEBUG0) { xpt_print(ccb->ccb_h.path, "enabling lun 0x%x on channel %d\n", lun, bus); } /* * Wait until we're not busy with the lun enables subsystem */ isp_tmlock(isp, "isp_enable_lun"); /* * This is as a good a place as any to check f/w capabilities. */ if (IS_FC(isp)) { if (ISP_CAP_TMODE(isp) == 0) { xpt_print(ccb->ccb_h.path, "firmware does not support target mode\n"); ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; goto done; } /* * We *could* handle non-SCCLUN f/w, but we'd have to * dork with our already fragile enable/disable code. */ if (ISP_CAP_SCCFW(isp) == 0) { xpt_print(ccb->ccb_h.path, "firmware not SCCLUN capable\n"); ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; goto done; } target_role = (FCPARAM(isp, bus)->role & ISP_ROLE_TARGET) != 0; } else { target_role = (SDPARAM(isp, bus)->role & ISP_ROLE_TARGET) != 0; } /* * Create the state pointer. * It should not already exist. */ tptr = get_lun_statep(isp, bus, lun); if (tptr) { ccb->ccb_h.status = CAM_LUN_ALRDY_ENA; goto done; } ccb->ccb_h.status = create_lun_state(isp, bus, ccb->ccb_h.path, &tptr); if (ccb->ccb_h.status != CAM_REQ_CMP) { goto done; } /* * We have a tricky maneuver to perform here. * * If target mode isn't already enabled here, * *and* our current role includes target mode, * we enable target mode here. * */ ISP_GET_PC(isp, bus, tm_enabled, tm_enabled); if (tm_enabled == 0 && target_role != 0) { if (isp_enable_target_mode(isp, bus)) { ccb->ccb_h.status = CAM_REQ_CMP_ERR; destroy_lun_state(isp, tptr); tptr = NULL; goto done; } tm_enabled = 1; } /* * Now check to see whether this bus is in target mode already. * * If not, a later role change into target mode will finish the job. */ if (tm_enabled == 0) { ISP_SET_PC(isp, bus, tm_enable_defer, 1); ccb->ccb_h.status = CAM_REQ_CMP; xpt_print(ccb->ccb_h.path, "Target Mode not enabled yet- lun enable deferred\n"); goto done1; } /* * Enable the lun. */ ccb->ccb_h.status = isp_enable_deferred(isp, bus, lun); done: if (ccb->ccb_h.status != CAM_REQ_CMP) { if (tptr) { destroy_lun_state(isp, tptr); tptr = NULL; } } else { tptr->enabled = 1; } done1: if (tptr) { rls_lun_statep(isp, tptr); } /* * And we're outta here.... */ isp_tmunlk(isp); xpt_done(ccb); } static cam_status isp_enable_deferred_luns(ispsoftc_t *isp, int bus) { tstate_t *tptr = NULL; struct tslist *lhp; int i, n; ISP_GET_PC(isp, bus, tm_enabled, i); if (i == 1) { return (CAM_REQ_CMP); } ISP_GET_PC(isp, bus, tm_enable_defer, i); if (i == 0) { return (CAM_REQ_CMP); } /* * If this succeeds, it will set tm_enable */ if (isp_enable_target_mode(isp, bus)) { return (CAM_REQ_CMP_ERR); } isp_tmlock(isp, "isp_enable_deferred_luns"); for (n = i = 0; i < LUN_HASH_SIZE; i++) { ISP_GET_PC_ADDR(isp, bus, lun_hash[i], lhp); SLIST_FOREACH(tptr, lhp, next) { tptr->hold++; if (tptr->enabled == 0) { if (isp_enable_deferred(isp, bus, xpt_path_lun_id(tptr->owner)) == CAM_REQ_CMP) { tptr->enabled = 1; n++; } } else { n++; } tptr->hold--; } } isp_tmunlk(isp); if (n == 0) { return (CAM_REQ_CMP_ERR); } ISP_SET_PC(isp, bus, tm_enable_defer, 0); return (CAM_REQ_CMP); } static cam_status isp_enable_deferred(ispsoftc_t *isp, int bus, lun_id_t lun) { cam_status status; int luns_already_enabled; ISP_GET_PC(isp, bus, tm_luns_enabled, luns_already_enabled); isp_prt(isp, ISP_LOGTINFO, "%s: bus %d lun %jx luns_enabled %d", __func__, bus, (uintmax_t)lun, luns_already_enabled); if (IS_24XX(isp) || (IS_FC(isp) && luns_already_enabled)) { status = CAM_REQ_CMP; } else { int cmd_cnt, not_cnt; if (IS_23XX(isp)) { cmd_cnt = DFLT_CMND_CNT; not_cnt = DFLT_INOT_CNT; } else { cmd_cnt = 64; not_cnt = 8; } status = CAM_REQ_INPROG; isp->isp_osinfo.rptr = &status; if (isp_lun_cmd(isp, RQSTYPE_ENABLE_LUN, bus, lun == CAM_LUN_WILDCARD? 0 : lun, cmd_cnt, not_cnt)) { status = CAM_RESRC_UNAVAIL; } else { mtx_sleep(&status, &isp->isp_lock, PRIBIO, "isp_enable_deferred", 0); } isp->isp_osinfo.rptr = NULL; } if (status == CAM_REQ_CMP) { ISP_SET_PC(isp, bus, tm_luns_enabled, 1); isp_prt(isp, ISP_LOGCONFIG|ISP_LOGTINFO, "bus %d lun %jx now enabled for target mode", bus, (uintmax_t)lun); } return (status); } static void isp_disable_lun(ispsoftc_t *isp, union ccb *ccb) { tstate_t *tptr = NULL; int bus; cam_status status; target_id_t target; lun_id_t lun; bus = XS_CHANNEL(ccb); target = ccb->ccb_h.target_id; lun = ccb->ccb_h.target_lun; ISP_PATH_PRT(isp, ISP_LOGTDEBUG0|ISP_LOGCONFIG, ccb->ccb_h.path, "disabling lun %u\n", lun); if (target != CAM_TARGET_WILDCARD && target != 0) { ccb->ccb_h.status = CAM_TID_INVALID; xpt_done(ccb); return; } if (target == CAM_TARGET_WILDCARD && lun != CAM_LUN_WILDCARD) { ccb->ccb_h.status = CAM_LUN_INVALID; xpt_done(ccb); return; } if (target != CAM_TARGET_WILDCARD && lun == CAM_LUN_WILDCARD) { ccb->ccb_h.status = CAM_LUN_INVALID; xpt_done(ccb); return; } /* * See if we're busy disabling a lun now. */ isp_tmlock(isp, "isp_disable_lun"); status = CAM_REQ_INPROG; /* * Find the state pointer. */ if ((tptr = get_lun_statep(isp, bus, lun)) == NULL) { status = CAM_PATH_INVALID; goto done; } /* * If we're a 24XX card, we're done. */ if (IS_24XX(isp)) { status = CAM_REQ_CMP; goto done; } /* * For SCC FW, we only deal with lun zero. */ if (IS_FC(isp) && lun > 0) { status = CAM_REQ_CMP; goto done; } isp->isp_osinfo.rptr = &status; if (isp_lun_cmd(isp, RQSTYPE_ENABLE_LUN, bus, lun, 0, 0)) { status = CAM_RESRC_UNAVAIL; } else { mtx_sleep(ccb, &isp->isp_lock, PRIBIO, "isp_disable_lun", 0); } isp->isp_osinfo.rptr = NULL; done: if (status == CAM_REQ_CMP) { tptr->enabled = 0; /* * If we have no more luns enabled for this bus, * delete all tracked wwns for it (if we are FC), * and disable target mode. */ if (is_any_lun_enabled(isp, bus) == 0) { isp_del_all_wwn_entries(isp, bus); if (isp_disable_target_mode(isp, bus)) { status = CAM_REQ_CMP_ERR; } } } ccb->ccb_h.status = status; if (status == CAM_REQ_CMP) { destroy_lun_state(isp, tptr); xpt_print(ccb->ccb_h.path, "lun now disabled for target mode\n"); } else { if (tptr) rls_lun_statep(isp, tptr); } isp_tmunlk(isp); xpt_done(ccb); } static int isp_enable_target_mode(ispsoftc_t *isp, int bus) { int tm_enabled; ISP_GET_PC(isp, bus, tm_enabled, tm_enabled); if (tm_enabled != 0) { return (0); } if (IS_SCSI(isp)) { mbreg_t mbs; MBSINIT(&mbs, MBOX_ENABLE_TARGET_MODE, MBLOGALL, 0); mbs.param[0] = MBOX_ENABLE_TARGET_MODE; mbs.param[1] = ENABLE_TARGET_FLAG|ENABLE_TQING_FLAG; mbs.param[2] = bus << 7; if (isp_control(isp, ISPCTL_RUN_MBOXCMD, &mbs) < 0 || mbs.param[0] != MBOX_COMMAND_COMPLETE) { isp_prt(isp, ISP_LOGERR, "Unable to enable Target Role on Bus %d", bus); return (EIO); } } ISP_SET_PC(isp, bus, tm_enabled, 1); isp_prt(isp, ISP_LOGINFO, "Target Role enabled on Bus %d", bus); return (0); } static int isp_disable_target_mode(ispsoftc_t *isp, int bus) { int tm_enabled; ISP_GET_PC(isp, bus, tm_enabled, tm_enabled); if (tm_enabled == 0) { return (0); } if (IS_SCSI(isp)) { mbreg_t mbs; MBSINIT(&mbs, MBOX_ENABLE_TARGET_MODE, MBLOGALL, 0); mbs.param[2] = bus << 7; if (isp_control(isp, ISPCTL_RUN_MBOXCMD, &mbs) < 0 || mbs.param[0] != MBOX_COMMAND_COMPLETE) { isp_prt(isp, ISP_LOGERR, "Unable to disable Target Role on Bus %d", bus); return (EIO); } } ISP_SET_PC(isp, bus, tm_enabled, 0); isp_prt(isp, ISP_LOGINFO, "Target Role disabled on Bus %d", bus); return (0); } static void isp_ledone(ispsoftc_t *isp, lun_entry_t *lep) { uint32_t *rptr; rptr = isp->isp_osinfo.rptr; if (lep->le_status != LUN_OK) { isp_prt(isp, ISP_LOGERR, "ENABLE/MODIFY LUN returned 0x%x", lep->le_status); if (rptr) { *rptr = CAM_REQ_CMP_ERR; wakeup_one(rptr); } } else { if (rptr) { *rptr = CAM_REQ_CMP; wakeup_one(rptr); } } } static void isp_target_start_ctio(ispsoftc_t *isp, union ccb *ccb, enum Start_Ctio_How how) { int fctape, sendstatus, resid; tstate_t *tptr; fcparam *fcp; atio_private_data_t *atp; struct ccb_scsiio *cso; uint32_t dmaresult, handle, xfrlen, sense_length, tmp; uint8_t local[QENTRY_LEN]; tptr = get_lun_statep(isp, XS_CHANNEL(ccb), XS_LUN(ccb)); if (tptr == NULL) { tptr = get_lun_statep(isp, XS_CHANNEL(ccb), CAM_LUN_WILDCARD); if (tptr == NULL) { isp_prt(isp, ISP_LOGERR, "%s: [0x%x] cannot find tstate pointer", __func__, ccb->csio.tag_id); ccb->ccb_h.status = CAM_DEV_NOT_THERE; xpt_done(ccb); return; } } isp_prt(isp, ISP_LOGTDEBUG0, "%s: ENTRY[0x%x] how %u xfrlen %u sendstatus %d sense_len %u", __func__, ccb->csio.tag_id, how, ccb->csio.dxfer_len, (ccb->ccb_h.flags & CAM_SEND_STATUS) != 0, ((ccb->ccb_h.flags & CAM_SEND_SENSE)? ccb->csio.sense_len : 0)); switch (how) { case FROM_TIMER: case FROM_CAM: /* * Insert at the tail of the list, if any, waiting CTIO CCBs */ TAILQ_INSERT_TAIL(&tptr->waitq, &ccb->ccb_h, periph_links.tqe); break; case FROM_SRR: case FROM_CTIO_DONE: TAILQ_INSERT_HEAD(&tptr->waitq, &ccb->ccb_h, periph_links.tqe); break; } while (TAILQ_FIRST(&tptr->waitq) != NULL) { ccb = (union ccb *) TAILQ_FIRST(&tptr->waitq); TAILQ_REMOVE(&tptr->waitq, &ccb->ccb_h, periph_links.tqe); cso = &ccb->csio; xfrlen = cso->dxfer_len; if (xfrlen == 0) { if ((ccb->ccb_h.flags & CAM_SEND_STATUS) == 0) { ISP_PATH_PRT(isp, ISP_LOGERR, ccb->ccb_h.path, "a data transfer length of zero but no status to send is wrong\n"); ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); continue; } } atp = isp_find_atpd(isp, tptr, cso->tag_id); if (atp == NULL) { isp_prt(isp, ISP_LOGERR, "%s: [0x%x] cannot find private data adjunct in %s", __func__, cso->tag_id, __func__); isp_dump_atpd(isp, tptr); ccb->ccb_h.status = CAM_REQ_CMP_ERR; xpt_done(ccb); continue; } /* * Is this command a dead duck? */ if (atp->dead) { isp_prt(isp, ISP_LOGERR, "%s: [0x%x] not sending a CTIO for a dead command", __func__, cso->tag_id); ccb->ccb_h.status = CAM_REQ_ABORTED; xpt_done(ccb); continue; } /* * Check to make sure we're still in target mode. */ fcp = FCPARAM(isp, XS_CHANNEL(ccb)); if ((fcp->role & ISP_ROLE_TARGET) == 0) { isp_prt(isp, ISP_LOGERR, "%s: [0x%x] stopping sending a CTIO because we're no longer in target mode", __func__, cso->tag_id); ccb->ccb_h.status = CAM_PROVIDE_FAIL; xpt_done(ccb); continue; } /* * We're only handling ATPD_CCB_OUTSTANDING outstanding CCB at a time (one of which * could be split into two CTIOs to split data and status). */ if (atp->ctcnt >= ATPD_CCB_OUTSTANDING) { isp_prt(isp, ISP_LOGTINFO, "[0x%x] handling only %d CCBs at a time (flags for this ccb: 0x%x)", cso->tag_id, ATPD_CCB_OUTSTANDING, ccb->ccb_h.flags); TAILQ_INSERT_HEAD(&tptr->waitq, &ccb->ccb_h, periph_links.tqe); break; } /* * Does the initiator expect FC-Tape style responses? */ if ((atp->word3 & PRLI_WD3_RETRY) && fcp->fctape_enabled) { fctape = 1; } else { fctape = 0; } /* * If we already did the data xfer portion of a CTIO that sends data * and status, don't do it again and do the status portion now. */ if (atp->sendst) { isp_prt(isp, ISP_LOGTINFO, "[0x%x] now sending synthesized status orig_dl=%u xfered=%u bit=%u", cso->tag_id, atp->orig_datalen, atp->bytes_xfered, atp->bytes_in_transit); xfrlen = 0; /* we already did the data transfer */ atp->sendst = 0; } if (ccb->ccb_h.flags & CAM_SEND_STATUS) { sendstatus = 1; } else { sendstatus = 0; } if (ccb->ccb_h.flags & CAM_SEND_SENSE) { KASSERT((sendstatus != 0), ("how can you have CAM_SEND_SENSE w/o CAM_SEND_STATUS?")); /* * Sense length is not the entire sense data structure size. Periph * drivers don't seem to be setting sense_len to reflect the actual * size. We'll peek inside to get the right amount. */ sense_length = cso->sense_len; /* * This 'cannot' happen */ if (sense_length > (XCMD_SIZE - MIN_FCP_RESPONSE_SIZE)) { sense_length = XCMD_SIZE - MIN_FCP_RESPONSE_SIZE; } } else { sense_length = 0; } memset(local, 0, QENTRY_LEN); /* * Check for overflow */ tmp = atp->bytes_xfered + atp->bytes_in_transit + xfrlen; if (tmp > atp->orig_datalen) { isp_prt(isp, ISP_LOGERR, "%s: [0x%x] data overflow by %u bytes", __func__, cso->tag_id, tmp - atp->orig_datalen); ccb->ccb_h.status = CAM_DATA_RUN_ERR; xpt_done(ccb); continue; } if (IS_24XX(isp)) { ct7_entry_t *cto = (ct7_entry_t *) local; cto->ct_header.rqs_entry_type = RQSTYPE_CTIO7; cto->ct_header.rqs_entry_count = 1; cto->ct_header.rqs_seqno |= ATPD_SEQ_NOTIFY_CAM; ATPD_SET_SEQNO(cto, atp); cto->ct_nphdl = atp->nphdl; cto->ct_rxid = atp->tag; cto->ct_iid_lo = atp->portid; cto->ct_iid_hi = atp->portid >> 16; cto->ct_oxid = atp->oxid; cto->ct_vpidx = ISP_GET_VPIDX(isp, XS_CHANNEL(ccb)); cto->ct_timeout = 120; cto->ct_flags = atp->tattr << CT7_TASK_ATTR_SHIFT; /* * Mode 1, status, no data. Only possible when we are sending status, have * no data to transfer, and any sense data can fit into a ct7_entry_t. * * Mode 2, status, no data. We have to use this in the case that * the sense data won't fit into a ct7_entry_t. * */ if (sendstatus && xfrlen == 0) { cto->ct_flags |= CT7_SENDSTATUS | CT7_NO_DATA; resid = atp->orig_datalen - atp->bytes_xfered - atp->bytes_in_transit; if (sense_length <= MAXRESPLEN_24XX) { if (resid < 0) { cto->ct_resid = -resid; } else if (resid > 0) { cto->ct_resid = resid; } cto->ct_flags |= CT7_FLAG_MODE1; cto->ct_scsi_status = cso->scsi_status; if (resid < 0) { cto->ct_scsi_status |= (FCP_RESID_OVERFLOW << 8); } else if (resid > 0) { cto->ct_scsi_status |= (FCP_RESID_UNDERFLOW << 8); } if (fctape) { cto->ct_flags |= CT7_CONFIRM|CT7_EXPLCT_CONF; } if (sense_length) { cto->ct_scsi_status |= (FCP_SNSLEN_VALID << 8); cto->rsp.m1.ct_resplen = cto->ct_senselen = sense_length; memcpy(cto->rsp.m1.ct_resp, &cso->sense_data, sense_length); } } else { bus_addr_t addr; char buf[XCMD_SIZE]; fcp_rsp_iu_t *rp; if (atp->ests == NULL) { atp->ests = isp_get_ecmd(isp); if (atp->ests == NULL) { TAILQ_INSERT_HEAD(&tptr->waitq, &ccb->ccb_h, periph_links.tqe); break; } } memset(buf, 0, sizeof (buf)); rp = (fcp_rsp_iu_t *)buf; if (fctape) { cto->ct_flags |= CT7_CONFIRM|CT7_EXPLCT_CONF; rp->fcp_rsp_bits |= FCP_CONF_REQ; } cto->ct_flags |= CT7_FLAG_MODE2; rp->fcp_rsp_scsi_status = cso->scsi_status; if (resid < 0) { rp->fcp_rsp_resid = -resid; rp->fcp_rsp_bits |= FCP_RESID_OVERFLOW; } else if (resid > 0) { rp->fcp_rsp_resid = resid; rp->fcp_rsp_bits |= FCP_RESID_UNDERFLOW; } if (sense_length) { rp->fcp_rsp_snslen = sense_length; cto->ct_senselen = sense_length; rp->fcp_rsp_bits |= FCP_SNSLEN_VALID; isp_put_fcp_rsp_iu(isp, rp, atp->ests); memcpy(((fcp_rsp_iu_t *)atp->ests)->fcp_rsp_extra, &cso->sense_data, sense_length); } else { isp_put_fcp_rsp_iu(isp, rp, atp->ests); } if (isp->isp_dblev & ISP_LOGTDEBUG1) { isp_print_bytes(isp, "FCP Response Frame After Swizzling", MIN_FCP_RESPONSE_SIZE + sense_length, atp->ests); } addr = isp->isp_osinfo.ecmd_dma; addr += ((((isp_ecmd_t *)atp->ests) - isp->isp_osinfo.ecmd_base) * XCMD_SIZE); isp_prt(isp, ISP_LOGTDEBUG0, "%s: ests base %p vaddr %p ecmd_dma %jx addr %jx len %u", __func__, isp->isp_osinfo.ecmd_base, atp->ests, (uintmax_t) isp->isp_osinfo.ecmd_dma, (uintmax_t)addr, MIN_FCP_RESPONSE_SIZE + sense_length); cto->rsp.m2.ct_datalen = MIN_FCP_RESPONSE_SIZE + sense_length; cto->rsp.m2.ct_fcp_rsp_iudata.ds_base = DMA_LO32(addr); cto->rsp.m2.ct_fcp_rsp_iudata.ds_basehi = DMA_HI32(addr); cto->rsp.m2.ct_fcp_rsp_iudata.ds_count = MIN_FCP_RESPONSE_SIZE + sense_length; } if (sense_length) { isp_prt(isp, ISP_LOGTDEBUG0, "%s: CTIO7[0x%x] seq %u nc %d CDB0=%x sstatus=0x%x flags=0x%x resid=%d slen %u sense: %x %x/%x/%x", __func__, cto->ct_rxid, ATPD_GET_SEQNO(cto), ATPD_GET_NCAM(cto), atp->cdb0, cto->ct_scsi_status, cto->ct_flags, cto->ct_resid, sense_length, cso->sense_data.error_code, cso->sense_data.sense_buf[1], cso->sense_data.sense_buf[11], cso->sense_data.sense_buf[12]); } else { isp_prt(isp, ISP_LOGDEBUG0, "%s: CTIO7[0x%x] seq %u nc %d CDB0=%x sstatus=0x%x flags=0x%x resid=%d", __func__, cto->ct_rxid, ATPD_GET_SEQNO(cto), ATPD_GET_NCAM(cto), atp->cdb0, cto->ct_scsi_status, cto->ct_flags, cto->ct_resid); } atp->state = ATPD_STATE_LAST_CTIO; } /* * Mode 0 data transfers, *possibly* with status. */ if (xfrlen != 0) { cto->ct_flags |= CT7_FLAG_MODE0; if ((cso->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) { cto->ct_flags |= CT7_DATA_IN; } else { cto->ct_flags |= CT7_DATA_OUT; } cto->rsp.m0.reloff = atp->bytes_xfered + atp->bytes_in_transit; cto->rsp.m0.ct_xfrlen = xfrlen; #ifdef DEBUG if (ISP_FC_PC(isp, XS_CHANNEL(ccb))->inject_lost_data_frame && xfrlen > ISP_FC_PC(isp, XS_CHANNEL(ccb))->inject_lost_data_frame) { isp_prt(isp, ISP_LOGWARN, "%s: truncating data frame with xfrlen %d to %d", __func__, xfrlen, xfrlen - (xfrlen >> 2)); ISP_FC_PC(isp, XS_CHANNEL(ccb))->inject_lost_data_frame = 0; cto->rsp.m0.ct_xfrlen -= xfrlen >> 2; } #endif if (sendstatus) { resid = atp->orig_datalen - atp->bytes_xfered - xfrlen; if (cso->scsi_status == SCSI_STATUS_OK && resid == 0 /* && fctape == 0 */) { cto->ct_flags |= CT7_SENDSTATUS; atp->state = ATPD_STATE_LAST_CTIO; if (fctape) { cto->ct_flags |= CT7_CONFIRM|CT7_EXPLCT_CONF; } } else { atp->sendst = 1; /* send status later */ cto->ct_header.rqs_seqno &= ~ATPD_SEQ_NOTIFY_CAM; atp->state = ATPD_STATE_CTIO; } } else { atp->state = ATPD_STATE_CTIO; } isp_prt(isp, ISP_LOGTDEBUG0, "%s: CTIO7[0x%x] seq %u nc %d CDB0=%x sstatus=0x%x flags=0x%x xfrlen=%u off=%u", __func__, cto->ct_rxid, ATPD_GET_SEQNO(cto), ATPD_GET_NCAM(cto), atp->cdb0, cto->ct_scsi_status, cto->ct_flags, xfrlen, atp->bytes_xfered); } } else if (IS_FC(isp)) { ct2_entry_t *cto = (ct2_entry_t *) local; if (isp->isp_osinfo.sixtyfourbit) cto->ct_header.rqs_entry_type = RQSTYPE_CTIO3; else cto->ct_header.rqs_entry_type = RQSTYPE_CTIO2; cto->ct_header.rqs_entry_count = 1; cto->ct_header.rqs_seqno |= ATPD_SEQ_NOTIFY_CAM; ATPD_SET_SEQNO(cto, atp); if (ISP_CAP_2KLOGIN(isp) == 0) { ((ct2e_entry_t *)cto)->ct_iid = cso->init_id; } else { cto->ct_iid = cso->init_id; if (ISP_CAP_SCCFW(isp) == 0) { cto->ct_lun = ccb->ccb_h.target_lun; } } cto->ct_timeout = 10; cto->ct_rxid = cso->tag_id; /* * Mode 1, status, no data. Only possible when we are sending status, have * no data to transfer, and the sense length can fit in the ct7_entry. * * Mode 2, status, no data. We have to use this in the case the response * length won't fit into a ct2_entry_t. * * We'll fill out this structure with information as if this were a * Mode 1. The hardware layer will create the Mode 2 FCP RSP IU as * needed based upon this. */ if (sendstatus && xfrlen == 0) { cto->ct_flags |= CT2_SENDSTATUS | CT2_NO_DATA; resid = atp->orig_datalen - atp->bytes_xfered - atp->bytes_in_transit; if (sense_length <= MAXRESPLEN) { if (resid < 0) { cto->ct_resid = -resid; } else if (resid > 0) { cto->ct_resid = resid; } cto->ct_flags |= CT2_FLAG_MODE1; cto->rsp.m1.ct_scsi_status = cso->scsi_status; if (resid < 0) { cto->rsp.m1.ct_scsi_status |= CT2_DATA_OVER; } else if (resid > 0) { cto->rsp.m1.ct_scsi_status |= CT2_DATA_UNDER; } if (fctape) { cto->ct_flags |= CT2_CONFIRM; } if (sense_length) { cto->rsp.m1.ct_scsi_status |= CT2_SNSLEN_VALID; cto->rsp.m1.ct_resplen = cto->rsp.m1.ct_senselen = sense_length; memcpy(cto->rsp.m1.ct_resp, &cso->sense_data, sense_length); } } else { bus_addr_t addr; char buf[XCMD_SIZE]; fcp_rsp_iu_t *rp; if (atp->ests == NULL) { atp->ests = isp_get_ecmd(isp); if (atp->ests == NULL) { TAILQ_INSERT_HEAD(&tptr->waitq, &ccb->ccb_h, periph_links.tqe); break; } } memset(buf, 0, sizeof (buf)); rp = (fcp_rsp_iu_t *)buf; if (fctape) { cto->ct_flags |= CT2_CONFIRM; rp->fcp_rsp_bits |= FCP_CONF_REQ; } cto->ct_flags |= CT2_FLAG_MODE2; rp->fcp_rsp_scsi_status = cso->scsi_status; if (resid < 0) { rp->fcp_rsp_resid = -resid; rp->fcp_rsp_bits |= FCP_RESID_OVERFLOW; } else if (resid > 0) { rp->fcp_rsp_resid = resid; rp->fcp_rsp_bits |= FCP_RESID_UNDERFLOW; } if (sense_length) { rp->fcp_rsp_snslen = sense_length; rp->fcp_rsp_bits |= FCP_SNSLEN_VALID; isp_put_fcp_rsp_iu(isp, rp, atp->ests); memcpy(((fcp_rsp_iu_t *)atp->ests)->fcp_rsp_extra, &cso->sense_data, sense_length); } else { isp_put_fcp_rsp_iu(isp, rp, atp->ests); } if (isp->isp_dblev & ISP_LOGTDEBUG1) { isp_print_bytes(isp, "FCP Response Frame After Swizzling", MIN_FCP_RESPONSE_SIZE + sense_length, atp->ests); } addr = isp->isp_osinfo.ecmd_dma; addr += ((((isp_ecmd_t *)atp->ests) - isp->isp_osinfo.ecmd_base) * XCMD_SIZE); isp_prt(isp, ISP_LOGTDEBUG0, "%s: ests base %p vaddr %p ecmd_dma %jx addr %jx len %u", __func__, isp->isp_osinfo.ecmd_base, atp->ests, (uintmax_t) isp->isp_osinfo.ecmd_dma, (uintmax_t)addr, MIN_FCP_RESPONSE_SIZE + sense_length); cto->rsp.m2.ct_datalen = MIN_FCP_RESPONSE_SIZE + sense_length; if (isp->isp_osinfo.sixtyfourbit) { cto->rsp.m2.u.ct_fcp_rsp_iudata_64.ds_base = DMA_LO32(addr); cto->rsp.m2.u.ct_fcp_rsp_iudata_64.ds_basehi = DMA_HI32(addr); cto->rsp.m2.u.ct_fcp_rsp_iudata_64.ds_count = MIN_FCP_RESPONSE_SIZE + sense_length; } else { cto->rsp.m2.u.ct_fcp_rsp_iudata_32.ds_base = DMA_LO32(addr); cto->rsp.m2.u.ct_fcp_rsp_iudata_32.ds_count = MIN_FCP_RESPONSE_SIZE + sense_length; } } if (sense_length) { isp_prt(isp, ISP_LOGTDEBUG0, "%s: CTIO2[0x%x] seq %u nc %d CDB0=%x sstatus=0x%x flags=0x%x resid=%d sense: %x %x/%x/%x", __func__, cto->ct_rxid, ATPD_GET_SEQNO(cto), ATPD_GET_NCAM(cto), atp->cdb0, cso->scsi_status, cto->ct_flags, cto->ct_resid, cso->sense_data.error_code, cso->sense_data.sense_buf[1], cso->sense_data.sense_buf[11], cso->sense_data.sense_buf[12]); } else { isp_prt(isp, ISP_LOGTDEBUG0, "%s: CTIO2[0x%x] seq %u nc %d CDB0=%x sstatus=0x%x flags=0x%x resid=%d", __func__, cto->ct_rxid, ATPD_GET_SEQNO(cto), ATPD_GET_NCAM(cto), atp->cdb0, cso->scsi_status, cto->ct_flags, cto->ct_resid); } atp->state = ATPD_STATE_LAST_CTIO; } if (xfrlen != 0) { cto->ct_flags |= CT2_FLAG_MODE0; if ((cso->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) { cto->ct_flags |= CT2_DATA_IN; } else { cto->ct_flags |= CT2_DATA_OUT; } cto->ct_reloff = atp->bytes_xfered + atp->bytes_in_transit; cto->rsp.m0.ct_xfrlen = xfrlen; if (sendstatus) { resid = atp->orig_datalen - atp->bytes_xfered - xfrlen; if (cso->scsi_status == SCSI_STATUS_OK && resid == 0 /*&& fctape == 0*/) { cto->ct_flags |= CT2_SENDSTATUS; atp->state = ATPD_STATE_LAST_CTIO; if (fctape) { cto->ct_flags |= CT2_CONFIRM; } } else { atp->sendst = 1; /* send status later */ cto->ct_header.rqs_seqno &= ~ATPD_SEQ_NOTIFY_CAM; atp->state = ATPD_STATE_CTIO; } } else { atp->state = ATPD_STATE_CTIO; } } isp_prt(isp, ISP_LOGTDEBUG0, "%s: CTIO2[%x] seq %u nc %d CDB0=%x scsi status %x flags %x resid %d xfrlen %u offset %u", __func__, cto->ct_rxid, ATPD_GET_SEQNO(cto), ATPD_GET_NCAM(cto), atp->cdb0, cso->scsi_status, cto->ct_flags, cto->ct_resid, cso->dxfer_len, atp->bytes_xfered); } else { ct_entry_t *cto = (ct_entry_t *) local; cto->ct_header.rqs_entry_type = RQSTYPE_CTIO; cto->ct_header.rqs_entry_count = 1; cto->ct_header.rqs_seqno |= ATPD_SEQ_NOTIFY_CAM; ATPD_SET_SEQNO(cto, atp); cto->ct_iid = cso->init_id; cto->ct_iid |= XS_CHANNEL(ccb) << 7; cto->ct_tgt = ccb->ccb_h.target_id; cto->ct_lun = ccb->ccb_h.target_lun; cto->ct_fwhandle = cso->tag_id; if (atp->rxid) { cto->ct_tag_val = atp->rxid; cto->ct_flags |= CT_TQAE; } if (ccb->ccb_h.flags & CAM_DIS_DISCONNECT) { cto->ct_flags |= CT_NODISC; } if (cso->dxfer_len == 0) { cto->ct_flags |= CT_NO_DATA; } else if ((cso->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) { cto->ct_flags |= CT_DATA_IN; } else { cto->ct_flags |= CT_DATA_OUT; } if (ccb->ccb_h.flags & CAM_SEND_STATUS) { cto->ct_flags |= CT_SENDSTATUS|CT_CCINCR; cto->ct_scsi_status = cso->scsi_status; cto->ct_resid = atp->orig_datalen - atp->bytes_xfered - atp->bytes_in_transit - xfrlen; isp_prt(isp, ISP_LOGTDEBUG0, "%s: CTIO[%x] seq %u nc %d scsi status %x resid %d tag_id %x", __func__, cto->ct_fwhandle, ATPD_GET_SEQNO(cto), ATPD_GET_NCAM(cto), cso->scsi_status, cso->resid, cso->tag_id); } ccb->ccb_h.flags &= ~CAM_SEND_SENSE; cto->ct_timeout = 10; } if (isp_get_pcmd(isp, ccb)) { ISP_PATH_PRT(isp, ISP_LOGWARN, ccb->ccb_h.path, "out of PCMDs\n"); TAILQ_INSERT_HEAD(&tptr->waitq, &ccb->ccb_h, periph_links.tqe); break; } if (isp_allocate_xs_tgt(isp, ccb, &handle)) { ISP_PATH_PRT(isp, ISP_LOGWARN, ccb->ccb_h.path, "No XFLIST pointers for %s\n", __func__); TAILQ_INSERT_HEAD(&tptr->waitq, &ccb->ccb_h, periph_links.tqe); isp_free_pcmd(isp, ccb); break; } atp->bytes_in_transit += xfrlen; PISP_PCMD(ccb)->datalen = xfrlen; /* * Call the dma setup routines for this entry (and any subsequent * CTIOs) if there's data to move, and then tell the f/w it's got * new things to play with. As with isp_start's usage of DMA setup, * any swizzling is done in the machine dependent layer. Because * of this, we put the request onto the queue area first in native * format. */ if (IS_24XX(isp)) { ct7_entry_t *cto = (ct7_entry_t *) local; cto->ct_syshandle = handle; } else if (IS_FC(isp)) { ct2_entry_t *cto = (ct2_entry_t *) local; cto->ct_syshandle = handle; } else { ct_entry_t *cto = (ct_entry_t *) local; cto->ct_syshandle = handle; } dmaresult = ISP_DMASETUP(isp, cso, (ispreq_t *) local); if (dmaresult != CMD_QUEUED) { isp_destroy_tgt_handle(isp, handle); isp_free_pcmd(isp, ccb); if (dmaresult == CMD_EAGAIN) { TAILQ_INSERT_HEAD(&tptr->waitq, &ccb->ccb_h, periph_links.tqe); break; } ccb->ccb_h.status = CAM_REQ_CMP_ERR; xpt_done(ccb); continue; } isp->isp_nactive++; ccb->ccb_h.status = CAM_REQ_INPROG | CAM_SIM_QUEUED; if (xfrlen) { ccb->ccb_h.spriv_field0 = atp->bytes_xfered; } else { ccb->ccb_h.spriv_field0 = ~0; } atp->ctcnt++; atp->seqno++; } rls_lun_statep(isp, tptr); } static void isp_refire_putback_atio(void *arg) { union ccb *ccb = arg; - ispsoftc_t *isp = XS_ISP(ccb); - ISP_ASSERT_LOCKED(isp); + ISP_ASSERT_LOCKED((ispsoftc_t *)XS_ISP(ccb)); isp_target_putback_atio(ccb); } static void isp_refire_notify_ack(void *arg) { isp_tna_t *tp = arg; ispsoftc_t *isp = tp->isp; ISP_ASSERT_LOCKED(isp); if (isp_notify_ack(isp, tp->not)) { callout_schedule(&tp->timer, 5); } else { free(tp, M_DEVBUF); } } static void isp_target_putback_atio(union ccb *ccb) { ispsoftc_t *isp; struct ccb_scsiio *cso; void *qe; isp = XS_ISP(ccb); qe = isp_getrqentry(isp); if (qe == NULL) { xpt_print(ccb->ccb_h.path, "%s: Request Queue Overflow\n", __func__); callout_reset(&PISP_PCMD(ccb)->wdog, 10, isp_refire_putback_atio, ccb); return; } memset(qe, 0, QENTRY_LEN); cso = &ccb->csio; if (IS_FC(isp)) { at2_entry_t local, *at = &local; ISP_MEMZERO(at, sizeof (at2_entry_t)); at->at_header.rqs_entry_type = RQSTYPE_ATIO2; at->at_header.rqs_entry_count = 1; if (ISP_CAP_SCCFW(isp)) { at->at_scclun = (uint16_t) ccb->ccb_h.target_lun; } else { at->at_lun = (uint8_t) ccb->ccb_h.target_lun; } at->at_status = CT_OK; at->at_rxid = cso->tag_id; at->at_iid = cso->ccb_h.target_id; isp_put_atio2(isp, at, qe); } else { at_entry_t local, *at = &local; ISP_MEMZERO(at, sizeof (at_entry_t)); at->at_header.rqs_entry_type = RQSTYPE_ATIO; at->at_header.rqs_entry_count = 1; at->at_iid = cso->init_id; at->at_iid |= XS_CHANNEL(ccb) << 7; at->at_tgt = cso->ccb_h.target_id; at->at_lun = cso->ccb_h.target_lun; at->at_status = CT_OK; at->at_tag_val = AT_GET_TAG(cso->tag_id); at->at_handle = AT_GET_HANDLE(cso->tag_id); isp_put_atio(isp, at, qe); } ISP_TDQE(isp, "isp_target_putback_atio", isp->isp_reqidx, qe); ISP_SYNC_REQUEST(isp); isp_complete_ctio(ccb); } static void isp_complete_ctio(union ccb *ccb) { if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_INPROG) { ccb->ccb_h.status &= ~CAM_SIM_QUEUED; xpt_done(ccb); } } /* * Handle ATIO stuff that the generic code can't. * This means handling CDBs. */ static void isp_handle_platform_atio(ispsoftc_t *isp, at_entry_t *aep) { tstate_t *tptr; int status, bus; struct ccb_accept_tio *atiop; atio_private_data_t *atp; /* * The firmware status (except for the QLTM_SVALID bit) * indicates why this ATIO was sent to us. * * If QLTM_SVALID is set, the firmware has recommended Sense Data. * * If the DISCONNECTS DISABLED bit is set in the flags field, * we're still connected on the SCSI bus. */ status = aep->at_status; if ((status & ~QLTM_SVALID) == AT_PHASE_ERROR) { /* * Bus Phase Sequence error. We should have sense data * suggested by the f/w. I'm not sure quite yet what * to do about this for CAM. */ isp_prt(isp, ISP_LOGWARN, "PHASE ERROR"); isp_endcmd(isp, aep, SCSI_STATUS_BUSY, 0); return; } if ((status & ~QLTM_SVALID) != AT_CDB) { isp_prt(isp, ISP_LOGWARN, "bad atio (0x%x) leaked to platform", status); isp_endcmd(isp, aep, SCSI_STATUS_BUSY, 0); return; } bus = GET_BUS_VAL(aep->at_iid); tptr = get_lun_statep(isp, bus, aep->at_lun); if (tptr == NULL) { tptr = get_lun_statep(isp, bus, CAM_LUN_WILDCARD); if (tptr == NULL) { /* * Because we can't autofeed sense data back with * a command for parallel SCSI, we can't give back * a CHECK CONDITION. We'll give back a BUSY status * instead. This works out okay because the only * time we should, in fact, get this, is in the * case that somebody configured us without the * blackhole driver, so they get what they deserve. */ isp_endcmd(isp, aep, SCSI_STATUS_BUSY, 0); return; } } atp = isp_get_atpd(isp, tptr, aep->at_handle); atiop = (struct ccb_accept_tio *) SLIST_FIRST(&tptr->atios); if (atiop == NULL || atp == NULL) { /* * Because we can't autofeed sense data back with * a command for parallel SCSI, we can't give back * a CHECK CONDITION. We'll give back a QUEUE FULL status * instead. This works out okay because the only time we * should, in fact, get this, is in the case that we've * run out of ATIOS. */ xpt_print(tptr->owner, "no %s for lun %d from initiator %d\n", (atp == NULL && atiop == NULL)? "ATIOs *or* ATPS" : ((atp == NULL)? "ATPs" : "ATIOs"), aep->at_lun, aep->at_iid); isp_endcmd(isp, aep, SCSI_STATUS_BUSY, 0); if (atp) { isp_put_atpd(isp, tptr, atp); } rls_lun_statep(isp, tptr); return; } atp->rxid = aep->at_tag_val; atp->state = ATPD_STATE_ATIO; SLIST_REMOVE_HEAD(&tptr->atios, sim_links.sle); tptr->atio_count--; ISP_PATH_PRT(isp, ISP_LOGTDEBUG2, atiop->ccb_h.path, "Take FREE ATIO count now %d\n", tptr->atio_count); atiop->ccb_h.target_id = aep->at_tgt; atiop->ccb_h.target_lun = aep->at_lun; if (aep->at_flags & AT_NODISC) { atiop->ccb_h.flags |= CAM_DIS_DISCONNECT; } else { atiop->ccb_h.flags &= ~CAM_DIS_DISCONNECT; } if (status & QLTM_SVALID) { size_t amt = ISP_MIN(QLTM_SENSELEN, sizeof (atiop->sense_data)); atiop->sense_len = amt; ISP_MEMCPY(&atiop->sense_data, aep->at_sense, amt); } else { atiop->sense_len = 0; } atiop->init_id = GET_IID_VAL(aep->at_iid); atiop->cdb_len = aep->at_cdblen; ISP_MEMCPY(atiop->cdb_io.cdb_bytes, aep->at_cdb, aep->at_cdblen); atiop->ccb_h.status = CAM_CDB_RECVD; /* * Construct a tag 'id' based upon tag value (which may be 0..255) * and the handle (which we have to preserve). */ atiop->tag_id = atp->tag; if (aep->at_flags & AT_TQAE) { atiop->tag_action = aep->at_tag_type; atiop->ccb_h.status |= CAM_TAG_ACTION_VALID; } atp->orig_datalen = 0; atp->bytes_xfered = 0; atp->lun = aep->at_lun; atp->nphdl = aep->at_iid; atp->portid = PORT_NONE; atp->oxid = 0; atp->cdb0 = atiop->cdb_io.cdb_bytes[0]; atp->tattr = aep->at_tag_type; atp->state = ATPD_STATE_CAM; isp_prt(isp, ISP_LOGTDEBUG0, "ATIO[0x%x] CDB=0x%x lun %d", aep->at_tag_val, atp->cdb0, atp->lun); rls_lun_statep(isp, tptr); } static void isp_handle_platform_atio2(ispsoftc_t *isp, at2_entry_t *aep) { lun_id_t lun; fcportdb_t *lp; tstate_t *tptr; struct ccb_accept_tio *atiop; uint16_t nphdl; atio_private_data_t *atp; inot_private_data_t *ntp; /* * The firmware status (except for the QLTM_SVALID bit) * indicates why this ATIO was sent to us. * * If QLTM_SVALID is set, the firmware has recommended Sense Data. */ if ((aep->at_status & ~QLTM_SVALID) != AT_CDB) { isp_prt(isp, ISP_LOGWARN, "bogus atio (0x%x) leaked to platform", aep->at_status); isp_endcmd(isp, aep, SCSI_STATUS_BUSY, 0); return; } if (ISP_CAP_SCCFW(isp)) { lun = aep->at_scclun; } else { lun = aep->at_lun; } if (ISP_CAP_2KLOGIN(isp)) { nphdl = ((at2e_entry_t *)aep)->at_iid; } else { nphdl = aep->at_iid; } tptr = get_lun_statep(isp, 0, lun); if (tptr == NULL) { tptr = get_lun_statep(isp, 0, CAM_LUN_WILDCARD); if (tptr == NULL) { isp_prt(isp, ISP_LOGWARN, "%s: [0x%x] no state pointer for lun %jx or wildcard", __func__, aep->at_rxid, (uintmax_t)lun); if (lun == 0) { isp_endcmd(isp, aep, SCSI_STATUS_BUSY, 0); } else { isp_endcmd(isp, aep, SCSI_STATUS_CHECK_COND | ECMD_SVALID | (0x5 << 12) | (0x25 << 16), 0); } return; } } /* * Start any commands pending resources first. */ if (tptr->restart_queue) { inot_private_data_t *restart_queue = tptr->restart_queue; tptr->restart_queue = NULL; while (restart_queue) { ntp = restart_queue; restart_queue = ntp->rd.nt.nt_hba; isp_prt(isp, ISP_LOGTDEBUG0, "%s: restarting resrc deprived %x", __func__, ((at2_entry_t *)ntp->rd.data)->at_rxid); isp_handle_platform_atio2(isp, (at2_entry_t *) ntp->rd.data); isp_put_ntpd(isp, tptr, ntp); /* * If a recursion caused the restart queue to start to fill again, * stop and splice the new list on top of the old list and restore * it and go to noresrc. */ if (tptr->restart_queue) { ntp = tptr->restart_queue; tptr->restart_queue = restart_queue; while (restart_queue->rd.nt.nt_hba) { restart_queue = restart_queue->rd.nt.nt_hba; } restart_queue->rd.nt.nt_hba = ntp; goto noresrc; } } } atiop = (struct ccb_accept_tio *) SLIST_FIRST(&tptr->atios); if (atiop == NULL) { goto noresrc; } atp = isp_get_atpd(isp, tptr, aep->at_rxid); if (atp == NULL) { goto noresrc; } atp->state = ATPD_STATE_ATIO; SLIST_REMOVE_HEAD(&tptr->atios, sim_links.sle); tptr->atio_count--; isp_prt(isp, ISP_LOGTDEBUG2, "Take FREE ATIO count now %d", tptr->atio_count); atiop->ccb_h.target_id = FCPARAM(isp, 0)->isp_loopid; atiop->ccb_h.target_lun = lun; /* * We don't get 'suggested' sense data as we do with SCSI cards. */ atiop->sense_len = 0; if (ISP_CAP_2KLOGIN(isp)) { /* * NB: We could not possibly have 2K logins if we * NB: also did not have SCC FW. */ atiop->init_id = ((at2e_entry_t *)aep)->at_iid; } else { atiop->init_id = aep->at_iid; } /* * If we're not in the port database, add ourselves. */ if (!IS_2100(isp) && isp_find_pdb_by_loopid(isp, 0, atiop->init_id, &lp) == 0) { uint64_t iid = (((uint64_t) aep->at_wwpn[0]) << 48) | (((uint64_t) aep->at_wwpn[1]) << 32) | (((uint64_t) aep->at_wwpn[2]) << 16) | (((uint64_t) aep->at_wwpn[3]) << 0); /* * However, make sure we delete ourselves if otherwise * we were there but at a different loop id. */ if (isp_find_pdb_by_wwn(isp, 0, iid, &lp)) { isp_del_wwn_entry(isp, 0, iid, lp->handle, lp->portid); } isp_add_wwn_entry(isp, 0, iid, atiop->init_id, PORT_ANY, 0); } atiop->cdb_len = ATIO2_CDBLEN; ISP_MEMCPY(atiop->cdb_io.cdb_bytes, aep->at_cdb, ATIO2_CDBLEN); atiop->ccb_h.status = CAM_CDB_RECVD; atiop->tag_id = atp->tag; switch (aep->at_taskflags & ATIO2_TC_ATTR_MASK) { case ATIO2_TC_ATTR_SIMPLEQ: atiop->ccb_h.flags |= CAM_TAG_ACTION_VALID; atiop->tag_action = MSG_SIMPLE_Q_TAG; break; case ATIO2_TC_ATTR_HEADOFQ: atiop->ccb_h.flags |= CAM_TAG_ACTION_VALID; atiop->tag_action = MSG_HEAD_OF_Q_TAG; break; case ATIO2_TC_ATTR_ORDERED: atiop->ccb_h.flags |= CAM_TAG_ACTION_VALID; atiop->tag_action = MSG_ORDERED_Q_TAG; break; case ATIO2_TC_ATTR_ACAQ: /* ?? */ case ATIO2_TC_ATTR_UNTAGGED: default: atiop->tag_action = 0; break; } atp->orig_datalen = aep->at_datalen; atp->bytes_xfered = 0; atp->lun = lun; atp->nphdl = atiop->init_id; atp->sid = PORT_ANY; atp->oxid = aep->at_oxid; atp->cdb0 = aep->at_cdb[0]; atp->tattr = aep->at_taskflags & ATIO2_TC_ATTR_MASK; atp->state = ATPD_STATE_CAM; xpt_done((union ccb *)atiop); isp_prt(isp, ISP_LOGTDEBUG0, "ATIO2[0x%x] CDB=0x%x lun %jx datalen %u", aep->at_rxid, atp->cdb0, (uintmax_t)lun, atp->orig_datalen); rls_lun_statep(isp, tptr); return; noresrc: ntp = isp_get_ntpd(isp, tptr); if (ntp == NULL) { rls_lun_statep(isp, tptr); isp_endcmd(isp, aep, nphdl, 0, SCSI_STATUS_BUSY, 0); return; } memcpy(ntp->rd.data, aep, QENTRY_LEN); ntp->rd.nt.nt_hba = tptr->restart_queue; tptr->restart_queue = ntp; rls_lun_statep(isp, tptr); } static void isp_handle_platform_atio7(ispsoftc_t *isp, at7_entry_t *aep) { int cdbxlen; uint16_t lun, chan, nphdl = NIL_HANDLE; uint32_t did, sid; uint64_t wwn = INI_NONE; fcportdb_t *lp; tstate_t *tptr; struct ccb_accept_tio *atiop; atio_private_data_t *atp = NULL; atio_private_data_t *oatp; inot_private_data_t *ntp; did = (aep->at_hdr.d_id[0] << 16) | (aep->at_hdr.d_id[1] << 8) | aep->at_hdr.d_id[2]; sid = (aep->at_hdr.s_id[0] << 16) | (aep->at_hdr.s_id[1] << 8) | aep->at_hdr.s_id[2]; lun = (aep->at_cmnd.fcp_cmnd_lun[0] << 8) | aep->at_cmnd.fcp_cmnd_lun[1]; /* * Find the N-port handle, and Virtual Port Index for this command. * * If we can't, we're somewhat in trouble because we can't actually respond w/o that information. * We also, as a matter of course, need to know the WWN of the initiator too. */ if (ISP_CAP_MULTI_ID(isp)) { /* * Find the right channel based upon D_ID */ isp_find_chan_by_did(isp, did, &chan); if (chan == ISP_NOCHAN) { NANOTIME_T now; /* * If we don't recognizer our own D_DID, terminate the exchange, unless we're within 2 seconds of startup * It's a bit tricky here as we need to stash this command *somewhere*. */ GET_NANOTIME(&now); if (NANOTIME_SUB(&isp->isp_init_time, &now) > 2000000000ULL) { isp_prt(isp, ISP_LOGWARN, "%s: [RX_ID 0x%x] D_ID %x not found on any channel- dropping", __func__, aep->at_rxid, did); isp_endcmd(isp, aep, NIL_HANDLE, ISP_NOCHAN, ECMD_TERMINATE, 0); return; } tptr = get_lun_statep(isp, 0, 0); if (tptr == NULL) { tptr = get_lun_statep(isp, 0, CAM_LUN_WILDCARD); if (tptr == NULL) { isp_prt(isp, ISP_LOGWARN, "%s: [RX_ID 0x%x] D_ID %x not found on any channel and no tptr- dropping", __func__, aep->at_rxid, did); isp_endcmd(isp, aep, NIL_HANDLE, ISP_NOCHAN, ECMD_TERMINATE, 0); return; } } isp_prt(isp, ISP_LOGWARN, "%s: [RX_ID 0x%x] D_ID %x not found on any channel- deferring", __func__, aep->at_rxid, did); goto noresrc; } isp_prt(isp, ISP_LOGTDEBUG0, "%s: [RX_ID 0x%x] D_ID 0x%06x found on Chan %d for S_ID 0x%06x", __func__, aep->at_rxid, did, chan, sid); } else { chan = 0; } /* * Find the PDB entry for this initiator */ if (isp_find_pdb_by_sid(isp, chan, sid, &lp) == 0) { /* * If we're not in the port database terminate the exchange. */ isp_prt(isp, ISP_LOGTINFO, "%s: [RX_ID 0x%x] D_ID 0x%06x found on Chan %d for S_ID 0x%06x wasn't in PDB already", __func__, aep->at_rxid, did, chan, sid); isp_endcmd(isp, aep, NIL_HANDLE, chan, ECMD_TERMINATE, 0); return; } nphdl = lp->handle; wwn = lp->port_wwn; /* * Get the tstate pointer */ tptr = get_lun_statep(isp, chan, lun); if (tptr == NULL) { tptr = get_lun_statep(isp, chan, CAM_LUN_WILDCARD); if (tptr == NULL) { isp_prt(isp, ISP_LOGWARN, "%s: [0x%x] no state pointer for lun %d or wildcard", __func__, aep->at_rxid, lun); if (lun == 0) { isp_endcmd(isp, aep, nphdl, SCSI_STATUS_BUSY, 0); } else { isp_endcmd(isp, aep, nphdl, chan, SCSI_STATUS_CHECK_COND | ECMD_SVALID | (0x5 << 12) | (0x25 << 16), 0); } return; } } /* * Start any commands pending resources first. */ if (tptr->restart_queue) { inot_private_data_t *restart_queue = tptr->restart_queue; tptr->restart_queue = NULL; while (restart_queue) { ntp = restart_queue; restart_queue = ntp->rd.nt.nt_hba; isp_prt(isp, ISP_LOGTDEBUG0, "%s: restarting resrc deprived %x", __func__, ((at7_entry_t *)ntp->rd.data)->at_rxid); isp_handle_platform_atio7(isp, (at7_entry_t *) ntp->rd.data); isp_put_ntpd(isp, tptr, ntp); /* * If a recursion caused the restart queue to start to fill again, * stop and splice the new list on top of the old list and restore * it and go to noresrc. */ if (tptr->restart_queue) { isp_prt(isp, ISP_LOGTDEBUG0, "%s: restart queue refilling", __func__); if (restart_queue) { ntp = tptr->restart_queue; tptr->restart_queue = restart_queue; while (restart_queue->rd.nt.nt_hba) { restart_queue = restart_queue->rd.nt.nt_hba; } restart_queue->rd.nt.nt_hba = ntp; } goto noresrc; } } } /* * If the f/w is out of resources, just send a BUSY status back. */ if (aep->at_rxid == AT7_NORESRC_RXID) { rls_lun_statep(isp, tptr); isp_endcmd(isp, aep, nphdl, chan, SCSI_BUSY, 0); return; } /* * If we're out of resources, just send a BUSY status back. */ atiop = (struct ccb_accept_tio *) SLIST_FIRST(&tptr->atios); if (atiop == NULL) { isp_prt(isp, ISP_LOGTDEBUG0, "[0x%x] out of atios", aep->at_rxid); goto noresrc; } oatp = isp_find_atpd(isp, tptr, aep->at_rxid); if (oatp) { isp_prt(isp, ISP_LOGTDEBUG0, "[0x%x] tag wraparound in isp_handle_platforms_atio7 (N-Port Handle 0x%04x S_ID 0x%04x OX_ID 0x%04x) oatp state %d", aep->at_rxid, nphdl, sid, aep->at_hdr.ox_id, oatp->state); /* * It's not a "no resource" condition- but we can treat it like one */ goto noresrc; } atp = isp_get_atpd(isp, tptr, aep->at_rxid); if (atp == NULL) { isp_prt(isp, ISP_LOGTDEBUG0, "[0x%x] out of atps", aep->at_rxid); goto noresrc; } atp->word3 = lp->prli_word3; atp->state = ATPD_STATE_ATIO; SLIST_REMOVE_HEAD(&tptr->atios, sim_links.sle); tptr->atio_count--; ISP_PATH_PRT(isp, ISP_LOGTDEBUG2, atiop->ccb_h.path, "Take FREE ATIO count now %d\n", tptr->atio_count); atiop->init_id = nphdl; atiop->ccb_h.target_id = FCPARAM(isp, chan)->isp_loopid; atiop->ccb_h.target_lun = lun; atiop->sense_len = 0; cdbxlen = aep->at_cmnd.fcp_cmnd_alen_datadir >> FCP_CMND_ADDTL_CDBLEN_SHIFT; if (cdbxlen) { isp_prt(isp, ISP_LOGWARN, "additional CDBLEN ignored"); } cdbxlen = sizeof (aep->at_cmnd.cdb_dl.sf.fcp_cmnd_cdb); ISP_MEMCPY(atiop->cdb_io.cdb_bytes, aep->at_cmnd.cdb_dl.sf.fcp_cmnd_cdb, cdbxlen); atiop->cdb_len = cdbxlen; atiop->ccb_h.status = CAM_CDB_RECVD; atiop->tag_id = atp->tag; switch (aep->at_cmnd.fcp_cmnd_task_attribute & FCP_CMND_TASK_ATTR_MASK) { case FCP_CMND_TASK_ATTR_SIMPLE: atiop->ccb_h.flags |= CAM_TAG_ACTION_VALID; atiop->tag_action = MSG_SIMPLE_Q_TAG; break; case FCP_CMND_TASK_ATTR_HEAD: atiop->ccb_h.flags |= CAM_TAG_ACTION_VALID; atiop->tag_action = MSG_HEAD_OF_Q_TAG; break; case FCP_CMND_TASK_ATTR_ORDERED: atiop->ccb_h.flags |= CAM_TAG_ACTION_VALID; atiop->tag_action = MSG_ORDERED_Q_TAG; break; default: /* FALLTHROUGH */ case FCP_CMND_TASK_ATTR_ACA: case FCP_CMND_TASK_ATTR_UNTAGGED: atiop->tag_action = 0; break; } atp->orig_datalen = aep->at_cmnd.cdb_dl.sf.fcp_cmnd_dl; atp->bytes_xfered = 0; atp->lun = lun; atp->nphdl = nphdl; atp->portid = sid; atp->oxid = aep->at_hdr.ox_id; atp->rxid = aep->at_hdr.rx_id; atp->cdb0 = atiop->cdb_io.cdb_bytes[0]; atp->tattr = aep->at_cmnd.fcp_cmnd_task_attribute & FCP_CMND_TASK_ATTR_MASK; atp->state = ATPD_STATE_CAM; isp_prt(isp, ISP_LOGTDEBUG0, "ATIO7[0x%x] CDB=0x%x lun %d datalen %u", aep->at_rxid, atp->cdb0, lun, atp->orig_datalen); xpt_done((union ccb *)atiop); rls_lun_statep(isp, tptr); return; noresrc: if (atp) { isp_put_atpd(isp, tptr, atp); } ntp = isp_get_ntpd(isp, tptr); if (ntp == NULL) { rls_lun_statep(isp, tptr); isp_endcmd(isp, aep, nphdl, chan, SCSI_STATUS_BUSY, 0); return; } memcpy(ntp->rd.data, aep, QENTRY_LEN); ntp->rd.nt.nt_hba = tptr->restart_queue; tptr->restart_queue = ntp; rls_lun_statep(isp, tptr); } /* * Handle starting an SRR (sequence retransmit request) * We get here when we've gotten the immediate notify * and the return of all outstanding CTIOs for this * transaction. */ static void isp_handle_srr_start(ispsoftc_t *isp, tstate_t *tptr, atio_private_data_t *atp) { in_fcentry_24xx_t *inot; uint32_t srr_off, ccb_off, ccb_len, ccb_end; union ccb *ccb; inot = (in_fcentry_24xx_t *)atp->srr; srr_off = inot->in_srr_reloff_lo | (inot->in_srr_reloff_hi << 16); ccb = atp->srr_ccb; atp->srr_ccb = NULL; atp->nsrr++; if (ccb == NULL) { isp_prt(isp, ISP_LOGWARN, "SRR[0x%x] null ccb", atp->tag); goto fail; } ccb_off = ccb->ccb_h.spriv_field0; ccb_len = ccb->csio.dxfer_len; ccb_end = (ccb_off == ~0)? ~0 : ccb_off + ccb_len; switch (inot->in_srr_iu) { case R_CTL_INFO_SOLICITED_DATA: /* * We have to restart a FCP_DATA data out transaction */ atp->sendst = 0; atp->bytes_xfered = srr_off; if (ccb_len == 0) { isp_prt(isp, ISP_LOGWARN, "SRR[0x%x] SRR offset 0x%x but current CCB doesn't transfer data", atp->tag, srr_off); goto mdp; } if (srr_off < ccb_off || ccb_off > srr_off + ccb_len) { isp_prt(isp, ISP_LOGWARN, "SRR[0x%x] SRR offset 0x%x not covered by current CCB data range [0x%x..0x%x]", atp->tag, srr_off, ccb_off, ccb_end); goto mdp; } isp_prt(isp, ISP_LOGWARN, "SRR[0x%x] SRR offset 0x%x covered by current CCB data range [0x%x..0x%x]", atp->tag, srr_off, ccb_off, ccb_end); break; case R_CTL_INFO_COMMAND_STATUS: isp_prt(isp, ISP_LOGTINFO, "SRR[0x%x] Got an FCP RSP SRR- resending status", atp->tag); atp->sendst = 1; /* * We have to restart a FCP_RSP IU transaction */ break; case R_CTL_INFO_DATA_DESCRIPTOR: /* * We have to restart an FCP DATA in transaction */ isp_prt(isp, ISP_LOGWARN, "Got an FCP DATA IN SRR- dropping"); goto fail; default: isp_prt(isp, ISP_LOGWARN, "Got an unknown information (%x) SRR- dropping", inot->in_srr_iu); goto fail; } /* * We can't do anything until this is acked, so we might as well start it now. * We aren't going to do the usual asynchronous ack issue because we need * to make sure this gets on the wire first. */ if (isp_notify_ack(isp, inot)) { isp_prt(isp, ISP_LOGWARN, "could not push positive ack for SRR- you lose"); goto fail; } isp_target_start_ctio(isp, ccb, FROM_SRR); return; fail: inot->in_reserved = 1; isp_async(isp, ISPASYNC_TARGET_NOTIFY_ACK, inot); ccb->ccb_h.status &= ~CAM_STATUS_MASK; ccb->ccb_h.status |= CAM_REQ_CMP_ERR; isp_complete_ctio(ccb); return; mdp: if (isp_notify_ack(isp, inot)) { isp_prt(isp, ISP_LOGWARN, "could not push positive ack for SRR- you lose"); goto fail; } ccb->ccb_h.status &= ~CAM_STATUS_MASK; ccb->ccb_h.status = CAM_MESSAGE_RECV; /* * This is not a strict interpretation of MDP, but it's close */ ccb->csio.msg_ptr = &ccb->csio.sense_data.sense_buf[SSD_FULL_SIZE - 16]; ccb->csio.msg_len = 7; ccb->csio.msg_ptr[0] = MSG_EXTENDED; ccb->csio.msg_ptr[1] = 5; ccb->csio.msg_ptr[2] = 0; /* modify data pointer */ ccb->csio.msg_ptr[3] = srr_off >> 24; ccb->csio.msg_ptr[4] = srr_off >> 16; ccb->csio.msg_ptr[5] = srr_off >> 8; ccb->csio.msg_ptr[6] = srr_off; isp_complete_ctio(ccb); } static void isp_handle_srr_notify(ispsoftc_t *isp, void *inot_raw) { tstate_t *tptr; in_fcentry_24xx_t *inot = inot_raw; atio_private_data_t *atp; uint32_t tag = inot->in_rxid; uint32_t bus = inot->in_vpidx; if (!IS_24XX(isp)) { isp_async(isp, ISPASYNC_TARGET_NOTIFY_ACK, inot_raw); return; } tptr = get_lun_statep_from_tag(isp, bus, tag); if (tptr == NULL) { isp_prt(isp, ISP_LOGERR, "%s: cannot find tptr for tag %x in SRR Notify", __func__, tag); isp_async(isp, ISPASYNC_TARGET_NOTIFY_ACK, inot); return; } atp = isp_find_atpd(isp, tptr, tag); if (atp == NULL) { rls_lun_statep(isp, tptr); isp_prt(isp, ISP_LOGERR, "%s: cannot find adjunct for %x in SRR Notify", __func__, tag); isp_async(isp, ISPASYNC_TARGET_NOTIFY_ACK, inot); return; } atp->srr_notify_rcvd = 1; memcpy(atp->srr, inot, sizeof (atp->srr)); isp_prt(isp, ISP_LOGTINFO /* ISP_LOGTDEBUG0 */, "SRR[0x%x] inot->in_rxid flags 0x%x srr_iu=%x reloff 0x%x", inot->in_rxid, inot->in_flags, inot->in_srr_iu, inot->in_srr_reloff_lo | (inot->in_srr_reloff_hi << 16)); if (atp->srr_ccb) isp_handle_srr_start(isp, tptr, atp); rls_lun_statep(isp, tptr); } static void isp_handle_platform_ctio(ispsoftc_t *isp, void *arg) { union ccb *ccb; int sentstatus = 0, ok = 0, notify_cam = 0, resid = 0, failure = 0; tstate_t *tptr = NULL; atio_private_data_t *atp = NULL; int bus; uint32_t handle, moved_data = 0, data_requested; /* * CTIO handles are 16 bits. * CTIO2 and CTIO7 are 32 bits. */ if (IS_SCSI(isp)) { handle = ((ct_entry_t *)arg)->ct_syshandle; } else { handle = ((ct2_entry_t *)arg)->ct_syshandle; } ccb = isp_find_xs_tgt(isp, handle); if (ccb == NULL) { isp_print_bytes(isp, "null ccb in isp_handle_platform_ctio", QENTRY_LEN, arg); return; } isp_destroy_tgt_handle(isp, handle); data_requested = PISP_PCMD(ccb)->datalen; isp_free_pcmd(isp, ccb); if (isp->isp_nactive) { isp->isp_nactive--; } bus = XS_CHANNEL(ccb); tptr = get_lun_statep(isp, bus, XS_LUN(ccb)); if (tptr == NULL) { tptr = get_lun_statep(isp, bus, CAM_LUN_WILDCARD); } if (tptr == NULL) { isp_prt(isp, ISP_LOGERR, "%s: cannot find tptr for tag %x after I/O", __func__, ccb->csio.tag_id); return; } if (IS_24XX(isp)) { atp = isp_find_atpd(isp, tptr, ((ct7_entry_t *)arg)->ct_rxid); } else if (IS_FC(isp)) { atp = isp_find_atpd(isp, tptr, ((ct2_entry_t *)arg)->ct_rxid); } else { atp = isp_find_atpd(isp, tptr, ((ct_entry_t *)arg)->ct_fwhandle); } if (atp == NULL) { rls_lun_statep(isp, tptr); isp_prt(isp, ISP_LOGERR, "%s: cannot find adjunct for %x after I/O", __func__, ccb->csio.tag_id); return; } KASSERT((atp->ctcnt > 0), ("ctio count not greater than zero")); atp->bytes_in_transit -= data_requested; atp->ctcnt -= 1; ccb->ccb_h.status &= ~CAM_STATUS_MASK; if (IS_24XX(isp)) { ct7_entry_t *ct = arg; if (ct->ct_nphdl == CT7_SRR) { atp->srr_ccb = ccb; if (atp->srr_notify_rcvd) isp_handle_srr_start(isp, tptr, atp); rls_lun_statep(isp, tptr); return; } if (ct->ct_nphdl == CT_HBA_RESET) { failure = CAM_UNREC_HBA_ERROR; } else { sentstatus = ct->ct_flags & CT7_SENDSTATUS; ok = (ct->ct_nphdl == CT7_OK); notify_cam = (ct->ct_header.rqs_seqno & ATPD_SEQ_NOTIFY_CAM) != 0; if ((ct->ct_flags & CT7_DATAMASK) != CT7_NO_DATA) { resid = ct->ct_resid; moved_data = data_requested - resid; } } isp_prt(isp, ok? ISP_LOGTDEBUG0 : ISP_LOGWARN, "%s: CTIO7[%x] seq %u nc %d sts 0x%x flg 0x%x sns %d resid %d %s", __func__, ct->ct_rxid, ATPD_GET_SEQNO(ct), notify_cam, ct->ct_nphdl, ct->ct_flags, (ccb->ccb_h.status & CAM_SENT_SENSE) != 0, resid, sentstatus? "FIN" : "MID"); } else if (IS_FC(isp)) { ct2_entry_t *ct = arg; if (ct->ct_status == CT_SRR) { atp->srr_ccb = ccb; if (atp->srr_notify_rcvd) isp_handle_srr_start(isp, tptr, atp); rls_lun_statep(isp, tptr); isp_target_putback_atio(ccb); return; } if (ct->ct_status == CT_HBA_RESET) { failure = CAM_UNREC_HBA_ERROR; } else { sentstatus = ct->ct_flags & CT2_SENDSTATUS; ok = (ct->ct_status & ~QLTM_SVALID) == CT_OK; notify_cam = (ct->ct_header.rqs_seqno & ATPD_SEQ_NOTIFY_CAM) != 0; if ((ct->ct_flags & CT2_DATAMASK) != CT2_NO_DATA) { resid = ct->ct_resid; moved_data = data_requested - resid; } } isp_prt(isp, ok? ISP_LOGTDEBUG0 : ISP_LOGWARN, "%s: CTIO2[%x] seq %u nc %d sts 0x%x flg 0x%x sns %d resid %d %s", __func__, ct->ct_rxid, ATPD_GET_SEQNO(ct), notify_cam, ct->ct_status, ct->ct_flags, (ccb->ccb_h.status & CAM_SENT_SENSE) != 0, resid, sentstatus? "FIN" : "MID"); } else { ct_entry_t *ct = arg; if (ct->ct_status == (CT_HBA_RESET & 0xff)) { failure = CAM_UNREC_HBA_ERROR; } else { sentstatus = ct->ct_flags & CT_SENDSTATUS; ok = (ct->ct_status & ~QLTM_SVALID) == CT_OK; notify_cam = (ct->ct_header.rqs_seqno & ATPD_SEQ_NOTIFY_CAM) != 0; } if ((ct->ct_flags & CT_DATAMASK) != CT_NO_DATA) { resid = ct->ct_resid; moved_data = data_requested - resid; } isp_prt(isp, ISP_LOGTDEBUG0, "%s: CTIO[%x] seq %u nc %d tag %x S_ID 0x%x lun %d sts %x flg %x resid %d %s", __func__, ct->ct_fwhandle, ATPD_GET_SEQNO(ct), notify_cam, ct->ct_tag_val, ct->ct_iid, ct->ct_lun, ct->ct_status, ct->ct_flags, resid, sentstatus? "FIN" : "MID"); } if (ok) { if (moved_data) { atp->bytes_xfered += moved_data; ccb->csio.resid = atp->orig_datalen - atp->bytes_xfered - atp->bytes_in_transit; } if (sentstatus && (ccb->ccb_h.flags & CAM_SEND_SENSE)) { ccb->ccb_h.status |= CAM_SENT_SENSE; } ccb->ccb_h.status |= CAM_REQ_CMP; } else { notify_cam = 1; if (failure == CAM_UNREC_HBA_ERROR) ccb->ccb_h.status |= CAM_UNREC_HBA_ERROR; else ccb->ccb_h.status |= CAM_REQ_CMP_ERR; } atp->state = ATPD_STATE_PDON; rls_lun_statep(isp, tptr); /* * We never *not* notify CAM when there has been any error (ok == 0), * so we never need to do an ATIO putback if we're not notifying CAM. */ isp_prt(isp, ISP_LOGTDEBUG0, "%s CTIO[0x%x] done (ok=%d nc=%d nowsendstatus=%d ccb ss=%d)", (sentstatus)? " FINAL " : "MIDTERM ", atp->tag, ok, notify_cam, atp->sendst, (ccb->ccb_h.flags & CAM_SEND_STATUS) != 0); if (notify_cam == 0) { if (atp->sendst) { isp_target_start_ctio(isp, ccb, FROM_CTIO_DONE); } return; } /* * We're telling CAM we're done with this CTIO transaction. * * 24XX cards never need an ATIO put back. * * Other cards need one put back only on error. * In the latter case, a timeout will re-fire * and try again in case we didn't have * queue resources to do so at first. In any case, * once the putback is done we do the completion * call. */ if (ok || IS_24XX(isp)) { isp_complete_ctio(ccb); } else { isp_target_putback_atio(ccb); } } static void isp_handle_platform_notify_scsi(ispsoftc_t *isp, in_entry_t *inot) { isp_async(isp, ISPASYNC_TARGET_NOTIFY_ACK, inot); } static void isp_handle_platform_notify_fc(ispsoftc_t *isp, in_fcentry_t *inp) { int needack = 1; switch (inp->in_status) { case IN_PORT_LOGOUT: /* * XXX: Need to delete this initiator's WWN from the database * XXX: Need to send this LOGOUT upstream */ isp_prt(isp, ISP_LOGWARN, "port logout of S_ID 0x%x", inp->in_iid); break; case IN_PORT_CHANGED: isp_prt(isp, ISP_LOGWARN, "port changed for S_ID 0x%x", inp->in_iid); break; case IN_GLOBAL_LOGO: isp_del_all_wwn_entries(isp, 0); isp_prt(isp, ISP_LOGINFO, "all ports logged out"); break; case IN_ABORT_TASK: { tstate_t *tptr; uint16_t lun; uint32_t loopid; uint64_t wwn; atio_private_data_t *atp; fcportdb_t *lp; struct ccb_immediate_notify *inot = NULL; if (ISP_CAP_SCCFW(isp)) { lun = inp->in_scclun; } else { lun = inp->in_lun; } if (ISP_CAP_2KLOGIN(isp)) { loopid = ((in_fcentry_e_t *)inp)->in_iid; } else { loopid = inp->in_iid; } if (isp_find_pdb_by_loopid(isp, 0, loopid, &lp)) { wwn = lp->port_wwn; } else { wwn = INI_ANY; } tptr = get_lun_statep(isp, 0, lun); if (tptr == NULL) { tptr = get_lun_statep(isp, 0, CAM_LUN_WILDCARD); if (tptr == NULL) { isp_prt(isp, ISP_LOGWARN, "ABORT TASK for lun %u- but no tstate", lun); return; } } atp = isp_find_atpd(isp, tptr, inp->in_seqid); if (atp) { inot = (struct ccb_immediate_notify *) SLIST_FIRST(&tptr->inots); isp_prt(isp, ISP_LOGTDEBUG0, "ABORT TASK RX_ID %x WWN 0x%016llx state %d", inp->in_seqid, (unsigned long long) wwn, atp->state); if (inot) { tptr->inot_count--; SLIST_REMOVE_HEAD(&tptr->inots, sim_links.sle); ISP_PATH_PRT(isp, ISP_LOGTDEBUG2, inot->ccb_h.path, "%s: Take FREE INOT count now %d\n", __func__, tptr->inot_count); } else { ISP_PATH_PRT(isp, ISP_LOGWARN, tptr->owner, "out of INOT structures\n"); } } else { ISP_PATH_PRT(isp, ISP_LOGWARN, tptr->owner, "abort task RX_ID %x from wwn 0x%016llx, state unknown\n", inp->in_seqid, wwn); } if (inot) { isp_notify_t tmp, *nt = &tmp; ISP_MEMZERO(nt, sizeof (isp_notify_t)); nt->nt_hba = isp; nt->nt_tgt = FCPARAM(isp, 0)->isp_wwpn; nt->nt_wwn = wwn; nt->nt_nphdl = loopid; nt->nt_sid = PORT_ANY; nt->nt_did = PORT_ANY; nt->nt_lun = lun; nt->nt_need_ack = 1; nt->nt_channel = 0; nt->nt_ncode = NT_ABORT_TASK; nt->nt_lreserved = inot; isp_handle_platform_target_tmf(isp, nt); needack = 0; } rls_lun_statep(isp, tptr); break; } default: break; } if (needack) { isp_async(isp, ISPASYNC_TARGET_NOTIFY_ACK, inp); } } static void isp_handle_platform_notify_24xx(ispsoftc_t *isp, in_fcentry_24xx_t *inot) { uint16_t nphdl; uint16_t prli_options = 0; uint32_t portid; fcportdb_t *lp; uint8_t *ptr = NULL; uint64_t wwn; nphdl = inot->in_nphdl; if (nphdl != NIL_HANDLE) { portid = inot->in_portid_hi << 16 | inot->in_portid_lo; } else { portid = PORT_ANY; } switch (inot->in_status) { case IN24XX_ELS_RCVD: { char buf[16], *msg; int chan = ISP_GET_VPIDX(isp, inot->in_vpidx); /* * Note that we're just getting notification that an ELS was received * (possibly with some associated information sent upstream). This is * *not* the same as being given the ELS frame to accept or reject. */ switch (inot->in_status_subcode) { case LOGO: msg = "LOGO"; if (ISP_FW_NEWER_THAN(isp, 4, 0, 25)) { ptr = (uint8_t *)inot; /* point to unswizzled entry! */ wwn = (((uint64_t) ptr[IN24XX_LOGO_WWPN_OFF]) << 56) | (((uint64_t) ptr[IN24XX_LOGO_WWPN_OFF+1]) << 48) | (((uint64_t) ptr[IN24XX_LOGO_WWPN_OFF+2]) << 40) | (((uint64_t) ptr[IN24XX_LOGO_WWPN_OFF+3]) << 32) | (((uint64_t) ptr[IN24XX_LOGO_WWPN_OFF+4]) << 24) | (((uint64_t) ptr[IN24XX_LOGO_WWPN_OFF+5]) << 16) | (((uint64_t) ptr[IN24XX_LOGO_WWPN_OFF+6]) << 8) | (((uint64_t) ptr[IN24XX_LOGO_WWPN_OFF+7])); } else { wwn = INI_ANY; } isp_del_wwn_entry(isp, chan, wwn, nphdl, portid); break; case PRLO: msg = "PRLO"; break; case PLOGI: case PRLI: /* * Treat PRLI the same as PLOGI and make a database entry for it. */ if (inot->in_status_subcode == PLOGI) { msg = "PLOGI"; } else { prli_options = inot->in_prli_options; msg = "PRLI"; } if (ISP_FW_NEWER_THAN(isp, 4, 0, 25)) { ptr = (uint8_t *)inot; /* point to unswizzled entry! */ wwn = (((uint64_t) ptr[IN24XX_PLOGI_WWPN_OFF]) << 56) | (((uint64_t) ptr[IN24XX_PLOGI_WWPN_OFF+1]) << 48) | (((uint64_t) ptr[IN24XX_PLOGI_WWPN_OFF+2]) << 40) | (((uint64_t) ptr[IN24XX_PLOGI_WWPN_OFF+3]) << 32) | (((uint64_t) ptr[IN24XX_PLOGI_WWPN_OFF+4]) << 24) | (((uint64_t) ptr[IN24XX_PLOGI_WWPN_OFF+5]) << 16) | (((uint64_t) ptr[IN24XX_PLOGI_WWPN_OFF+6]) << 8) | (((uint64_t) ptr[IN24XX_PLOGI_WWPN_OFF+7])); } else { wwn = INI_NONE; } isp_add_wwn_entry(isp, chan, wwn, nphdl, portid, prli_options); break; case PDISC: msg = "PDISC"; break; case ADISC: msg = "ADISC"; break; default: ISP_SNPRINTF(buf, sizeof (buf), "ELS 0x%x", inot->in_status_subcode); msg = buf; break; } if (inot->in_flags & IN24XX_FLAG_PUREX_IOCB) { isp_prt(isp, ISP_LOGERR, "%s Chan %d ELS N-port handle %x PortID 0x%06x marked as needing a PUREX response", msg, chan, nphdl, portid); break; } isp_prt(isp, ISP_LOGTDEBUG0, "%s Chan %d ELS N-port handle %x PortID 0x%06x RX_ID 0x%x OX_ID 0x%x", msg, chan, nphdl, portid, inot->in_rxid, inot->in_oxid); isp_async(isp, ISPASYNC_TARGET_NOTIFY_ACK, inot); break; } case IN24XX_PORT_LOGOUT: ptr = "PORT LOGOUT"; if (isp_find_pdb_by_loopid(isp, ISP_GET_VPIDX(isp, inot->in_vpidx), nphdl, &lp)) { isp_del_wwn_entry(isp, ISP_GET_VPIDX(isp, inot->in_vpidx), lp->port_wwn, nphdl, lp->portid); } /* FALLTHROUGH */ case IN24XX_PORT_CHANGED: if (ptr == NULL) { ptr = "PORT CHANGED"; } /* FALLTHROUGH */ case IN24XX_LIP_RESET: if (ptr == NULL) { ptr = "LIP RESET"; } isp_prt(isp, ISP_LOGINFO, "Chan %d %s (sub-status 0x%x) for N-port handle 0x%x", ISP_GET_VPIDX(isp, inot->in_vpidx), ptr, inot->in_status_subcode, nphdl); /* * All subcodes here are irrelevant. What is relevant * is that we need to terminate all active commands from * this initiator (known by N-port handle). */ /* XXX IMPLEMENT XXX */ isp_async(isp, ISPASYNC_TARGET_NOTIFY_ACK, inot); break; case IN24XX_SRR_RCVD: #ifdef ISP_TARGET_MODE isp_handle_srr_notify(isp, inot); break; #else if (ptr == NULL) { ptr = "SRR RCVD"; } /* FALLTHROUGH */ #endif case IN24XX_LINK_RESET: if (ptr == NULL) { ptr = "LINK RESET"; } case IN24XX_LINK_FAILED: if (ptr == NULL) { ptr = "LINK FAILED"; } default: isp_prt(isp, ISP_LOGWARN, "Chan %d %s", ISP_GET_VPIDX(isp, inot->in_vpidx), ptr); isp_async(isp, ISPASYNC_TARGET_NOTIFY_ACK, inot); break; } } static int isp_handle_platform_target_notify_ack(ispsoftc_t *isp, isp_notify_t *mp) { if (isp->isp_state != ISP_RUNSTATE) { isp_prt(isp, ISP_LOGTINFO, "Notify Code 0x%x (qevalid=%d) acked- h/w not ready (dropping)", mp->nt_ncode, mp->nt_lreserved != NULL); return (0); } /* * This case is for a Task Management Function, which shows up as an ATIO7 entry. */ if (IS_24XX(isp) && mp->nt_lreserved && ((isphdr_t *)mp->nt_lreserved)->rqs_entry_type == RQSTYPE_ATIO) { ct7_entry_t local, *cto = &local; at7_entry_t *aep = (at7_entry_t *)mp->nt_lreserved; fcportdb_t *lp; uint32_t sid; uint16_t nphdl; sid = (aep->at_hdr.s_id[0] << 16) | (aep->at_hdr.s_id[1] << 8) | aep->at_hdr.s_id[2]; if (isp_find_pdb_by_sid(isp, mp->nt_channel, sid, &lp)) { nphdl = lp->handle; } else { nphdl = NIL_HANDLE; } ISP_MEMZERO(&local, sizeof (local)); cto->ct_header.rqs_entry_type = RQSTYPE_CTIO7; cto->ct_header.rqs_entry_count = 1; cto->ct_nphdl = nphdl; cto->ct_rxid = aep->at_rxid; cto->ct_vpidx = mp->nt_channel; cto->ct_iid_lo = sid; cto->ct_iid_hi = sid >> 16; cto->ct_oxid = aep->at_hdr.ox_id; cto->ct_flags = CT7_SENDSTATUS|CT7_NOACK|CT7_NO_DATA|CT7_FLAG_MODE1; cto->ct_flags |= (aep->at_ta_len >> 12) << CT7_TASK_ATTR_SHIFT; return (isp_target_put_entry(isp, &local)); } /* * This case is for a responding to an ABTS frame */ if (IS_24XX(isp) && mp->nt_lreserved && ((isphdr_t *)mp->nt_lreserved)->rqs_entry_type == RQSTYPE_ABTS_RCVD) { /* * Overload nt_need_ack here to mark whether we've terminated the associated command. */ if (mp->nt_need_ack) { uint8_t storage[QENTRY_LEN]; ct7_entry_t *cto = (ct7_entry_t *) storage; abts_t *abts = (abts_t *)mp->nt_lreserved; ISP_MEMZERO(cto, sizeof (ct7_entry_t)); isp_prt(isp, ISP_LOGTDEBUG0, "%s: [%x] terminating after ABTS received", __func__, abts->abts_rxid_task); cto->ct_header.rqs_entry_type = RQSTYPE_CTIO7; cto->ct_header.rqs_entry_count = 1; cto->ct_nphdl = mp->nt_nphdl; cto->ct_rxid = abts->abts_rxid_task; cto->ct_iid_lo = mp->nt_sid; cto->ct_iid_hi = mp->nt_sid >> 16; cto->ct_oxid = abts->abts_ox_id; cto->ct_vpidx = mp->nt_channel; cto->ct_flags = CT7_NOACK|CT7_TERMINATE; if (isp_target_put_entry(isp, cto)) { return (ENOMEM); } mp->nt_need_ack = 0; } if (isp_acknak_abts(isp, mp->nt_lreserved, 0) == ENOMEM) { return (ENOMEM); } else { return (0); } } /* * Handle logout cases here */ if (mp->nt_ncode == NT_GLOBAL_LOGOUT) { isp_del_all_wwn_entries(isp, mp->nt_channel); } if (mp->nt_ncode == NT_LOGOUT) { if (!IS_2100(isp) && IS_FC(isp)) { isp_del_wwn_entries(isp, mp); } } /* * General purpose acknowledgement */ if (mp->nt_need_ack) { isp_prt(isp, ISP_LOGTINFO, "Notify Code 0x%x (qevalid=%d) being acked", mp->nt_ncode, mp->nt_lreserved != NULL); /* * Don't need to use the guaranteed send because the caller can retry */ return (isp_notify_ack(isp, mp->nt_lreserved)); } return (0); } /* * Handle task management functions. * * We show up here with a notify structure filled out. * * The nt_lreserved tag points to the original queue entry */ static void isp_handle_platform_target_tmf(ispsoftc_t *isp, isp_notify_t *notify) { tstate_t *tptr; fcportdb_t *lp; struct ccb_immediate_notify *inot; inot_private_data_t *ntp = NULL; lun_id_t lun; isp_prt(isp, ISP_LOGTDEBUG0, "%s: code 0x%x sid 0x%x tagval 0x%016llx chan %d lun 0x%x", __func__, notify->nt_ncode, notify->nt_sid, (unsigned long long) notify->nt_tagval, notify->nt_channel, notify->nt_lun); /* * NB: This assignment is necessary because of tricky type conversion. * XXX: This is tricky and I need to check this. If the lun isn't known * XXX: for the task management function, it does not of necessity follow * XXX: that it should go up stream to the wildcard listener. */ if (notify->nt_lun == LUN_ANY) { lun = CAM_LUN_WILDCARD; } else { lun = notify->nt_lun; } tptr = get_lun_statep(isp, notify->nt_channel, lun); if (tptr == NULL) { tptr = get_lun_statep(isp, notify->nt_channel, CAM_LUN_WILDCARD); if (tptr == NULL) { isp_prt(isp, ISP_LOGWARN, "%s: no state pointer found for chan %d lun %#jx", __func__, notify->nt_channel, (uintmax_t)lun); goto bad; } } inot = (struct ccb_immediate_notify *) SLIST_FIRST(&tptr->inots); if (inot == NULL) { isp_prt(isp, ISP_LOGWARN, "%s: out of immediate notify structures for chan %d lun %#jx", __func__, notify->nt_channel, (uintmax_t)lun); goto bad; } if (isp_find_pdb_by_sid(isp, notify->nt_channel, notify->nt_sid, &lp) == 0) { inot->initiator_id = CAM_TARGET_WILDCARD; } else { inot->initiator_id = lp->handle; } inot->seq_id = notify->nt_tagval; inot->tag_id = notify->nt_tagval >> 32; switch (notify->nt_ncode) { case NT_ABORT_TASK: isp_target_mark_aborted_early(isp, tptr, inot->tag_id); inot->arg = MSG_ABORT_TASK; break; case NT_ABORT_TASK_SET: isp_target_mark_aborted_early(isp, tptr, TAG_ANY); inot->arg = MSG_ABORT_TASK_SET; break; case NT_CLEAR_ACA: inot->arg = MSG_CLEAR_ACA; break; case NT_CLEAR_TASK_SET: inot->arg = MSG_CLEAR_TASK_SET; break; case NT_LUN_RESET: inot->arg = MSG_LOGICAL_UNIT_RESET; break; case NT_TARGET_RESET: inot->arg = MSG_TARGET_RESET; break; default: isp_prt(isp, ISP_LOGWARN, "%s: unknown TMF code 0x%x for chan %d lun %#jx", __func__, notify->nt_ncode, notify->nt_channel, (uintmax_t)lun); goto bad; } ntp = isp_get_ntpd(isp, tptr); if (ntp == NULL) { isp_prt(isp, ISP_LOGWARN, "%s: out of inotify private structures", __func__); goto bad; } ISP_MEMCPY(&ntp->rd.nt, notify, sizeof (isp_notify_t)); if (notify->nt_lreserved) { ISP_MEMCPY(&ntp->rd.data, notify->nt_lreserved, QENTRY_LEN); ntp->rd.nt.nt_lreserved = &ntp->rd.data; } ntp->rd.seq_id = notify->nt_tagval; ntp->rd.tag_id = notify->nt_tagval >> 32; tptr->inot_count--; SLIST_REMOVE_HEAD(&tptr->inots, sim_links.sle); rls_lun_statep(isp, tptr); ISP_PATH_PRT(isp, ISP_LOGTDEBUG2, inot->ccb_h.path, "%s: Take FREE INOT count now %d\n", __func__, tptr->inot_count); inot->ccb_h.status = CAM_MESSAGE_RECV; xpt_done((union ccb *)inot); return; bad: if (tptr) { rls_lun_statep(isp, tptr); } if (notify->nt_need_ack && notify->nt_lreserved) { if (((isphdr_t *)notify->nt_lreserved)->rqs_entry_type == RQSTYPE_ABTS_RCVD) { if (isp_acknak_abts(isp, notify->nt_lreserved, ENOMEM)) { isp_prt(isp, ISP_LOGWARN, "you lose- unable to send an ACKNAK"); } } else { isp_async(isp, ISPASYNC_TARGET_NOTIFY_ACK, notify->nt_lreserved); } } } /* * Find the associated private data and mark it as dead so * we don't try to work on it any further. */ static void isp_target_mark_aborted(ispsoftc_t *isp, union ccb *ccb) { tstate_t *tptr; atio_private_data_t *atp; union ccb *accb = ccb->cab.abort_ccb; tptr = get_lun_statep(isp, XS_CHANNEL(accb), XS_LUN(accb)); if (tptr == NULL) { tptr = get_lun_statep(isp, XS_CHANNEL(accb), CAM_LUN_WILDCARD); if (tptr == NULL) { ccb->ccb_h.status = CAM_REQ_INVALID; return; } } atp = isp_find_atpd(isp, tptr, accb->atio.tag_id); if (atp == NULL) { ccb->ccb_h.status = CAM_REQ_INVALID; } else { atp->dead = 1; ccb->ccb_h.status = CAM_REQ_CMP; } rls_lun_statep(isp, tptr); } static void isp_target_mark_aborted_early(ispsoftc_t *isp, tstate_t *tptr, uint32_t tag_id) { atio_private_data_t *atp; inot_private_data_t *restart_queue = tptr->restart_queue; /* * First, clean any commands pending restart */ tptr->restart_queue = NULL; while (restart_queue) { uint32_t this_tag_id; inot_private_data_t *ntp = restart_queue; restart_queue = ntp->rd.nt.nt_hba; if (IS_24XX(isp)) { this_tag_id = ((at7_entry_t *)ntp->rd.data)->at_rxid; } else { this_tag_id = ((at2_entry_t *)ntp->rd.data)->at_rxid; } if ((uint64_t)tag_id == TAG_ANY || tag_id == this_tag_id) { isp_put_ntpd(isp, tptr, ntp); } else { ntp->rd.nt.nt_hba = tptr->restart_queue; tptr->restart_queue = ntp; } } /* * Now mark other ones dead as well. */ for (atp = tptr->atpool; atp < &tptr->atpool[ATPDPSIZE]; atp++) { if ((uint64_t)tag_id == TAG_ANY || atp->tag == tag_id) { atp->dead = 1; } } } #ifdef ISP_INTERNAL_TARGET //#define ISP_SEPARATE_STATUS 1 #define ISP_MULTI_CCBS 1 #if defined(ISP_MULTI_CCBS) && !defined(ISP_SEPARATE_STATUS) #define ISP_SEPARATE_STATUS 1 #endif typedef struct periph_private_data_t { union ccb *ccb; /* original ATIO or Immediate Notify */ unsigned long offset; /* current offset */ int sequence; /* current CTIO sequence */ int ctio_cnt; /* current # of ctio's outstanding */ int status_sent : 1, on_queue : 1; /* on restart queue */ } ppd_t; /* * Each ATIO we allocate will have periph private data associated with it * that maintains per-command state. This private to each ATIO. */ #define ATIO_PPD(ccb) ((ppd_t *)(((struct ccb_hdr *)ccb)->ppriv_ptr0)) /* * Each CTIO we send downstream will get a pointer to the ATIO itself * so that on completion we can retrieve that pointer. */ #define ccb_atio ppriv_ptr1 #define ccb_inot ppriv_ptr1 /* * Each CTIO we send downstream will contain a sequence number */ #define CTIO_SEQ(ccb) ccb->ccb_h.ppriv_field0 #define MAX_ISP_TARG_TRANSFER (2 << 20) #define NISP_TARG_CMDS 64 #define NISP_TARG_NOTIFIES 64 #define DISK_SHIFT 9 #define JUNK_SIZE 256 #define MULTI_CCB_DATA_LIM 8192 //#define MULTI_CCB_DATA_CNT 64 #define MULTI_CCB_DATA_CNT 8 extern u_int vm_kmem_size; static int ca; static uint32_t disk_size; static uint8_t *disk_data = NULL; static uint8_t *junk_data; static MALLOC_DEFINE(M_ISPTARG, "ISPTARG", "ISP TARGET data"); struct isptarg_softc { /* CCBs (CTIOs, ATIOs, INOTs) pending on the controller */ struct isp_ccbq work_queue; struct isp_ccbq rework_queue; struct isp_ccbq running_queue; struct isp_ccbq inot_queue; struct cam_periph *periph; struct cam_path *path; ispsoftc_t *isp; }; static periph_ctor_t isptargctor; static periph_dtor_t isptargdtor; static periph_start_t isptargstart; static periph_init_t isptarginit; static void isptarg_done(struct cam_periph *, union ccb *); static void isptargasync(void *, u_int32_t, struct cam_path *, void *); static int isptarg_rwparm(uint8_t *, uint8_t *, uint64_t, uint32_t, uint8_t **, uint32_t *, int *); static struct periph_driver isptargdriver = { isptarginit, "isptarg", TAILQ_HEAD_INITIALIZER(isptargdriver.units), 0 }; static void isptarginit(void) { } static void isptargnotify(ispsoftc_t *isp, union ccb *iccb, struct ccb_immediate_notify *inot) { struct ccb_notify_acknowledge *ack = &iccb->cna2; ISP_PATH_PRT(isp, ISP_LOGTDEBUG0, inot->ccb_h.path, "%s: [0x%x] immediate notify for 0x%x from 0x%x status 0x%x arg 0x%x\n", __func__, inot->tag_id, inot->initiator_id, inot->seq_id, inot->ccb_h.status, inot->arg); ack->ccb_h.func_code = XPT_NOTIFY_ACKNOWLEDGE; ack->ccb_h.flags = 0; ack->ccb_h.retry_count = 0; ack->ccb_h.cbfcnp = isptarg_done; ack->ccb_h.timeout = 0; ack->ccb_h.ccb_inot = inot; ack->tag_id = inot->tag_id; ack->seq_id = inot->seq_id; ack->initiator_id = inot->initiator_id; xpt_action(iccb); } static void isptargstart(struct cam_periph *periph, union ccb *iccb) { const uint8_t niliqd[SHORT_INQUIRY_LENGTH] = { 0x7f, 0x0, 0x5, 0x2, 32, 0, 0, 0x32, 'F', 'R', 'E', 'E', 'B', 'S', 'D', ' ', 'S', 'C', 'S', 'I', ' ', 'N', 'U', 'L', 'L', ' ', 'D', 'E', 'V', 'I', 'C', 'E', '0', '0', '0', '1' }; const uint8_t iqd[SHORT_INQUIRY_LENGTH] = { 0, 0x0, 0x5, 0x2, 32, 0, 0, 0x32, 'F', 'R', 'E', 'E', 'B', 'S', 'D', ' ', 'S', 'C', 'S', 'I', ' ', 'M', 'E', 'M', 'O', 'R', 'Y', ' ', 'D', 'I', 'S', 'K', '0', '0', '0', '1' }; int r, i, more = 0, last, is_data_cmd = 0, is_write; char *queue; struct isptarg_softc *softc = periph->softc; struct ccb_scsiio *csio; lun_id_t return_lun; struct ccb_accept_tio *atio; uint8_t *cdb, *ptr, status; uint8_t *data_ptr; uint32_t data_len, flags; struct ccb_hdr *ccbh; mtx_assert(periph->sim->mtx, MA_OWNED); ISP_PATH_PRT(softc->isp, ISP_LOGTDEBUG1, iccb->ccb_h.path, "%s: function code 0x%x INOTQ=%c WORKQ=%c REWORKQ=%c\n", __func__, iccb->ccb_h.func_code, TAILQ_FIRST(&softc->inot_queue)? 'y' : 'n', TAILQ_FIRST(&softc->work_queue)? 'y' : 'n', TAILQ_FIRST(&softc->rework_queue)? 'y' : 'n'); /* * Check for immediate notifies first */ ccbh = TAILQ_FIRST(&softc->inot_queue); if (ccbh) { TAILQ_REMOVE(&softc->inot_queue, ccbh, periph_links.tqe); if (TAILQ_FIRST(&softc->inot_queue) || TAILQ_FIRST(&softc->work_queue) || TAILQ_FIRST(&softc->rework_queue)) { xpt_schedule(periph, 1); } isptargnotify(softc->isp, iccb, (struct ccb_immediate_notify *)ccbh); return; } /* * Check the rework (continuation) work queue first. */ ccbh = TAILQ_FIRST(&softc->rework_queue); if (ccbh) { atio = (struct ccb_accept_tio *)ccbh; TAILQ_REMOVE(&softc->rework_queue, ccbh, periph_links.tqe); more = TAILQ_FIRST(&softc->work_queue) || TAILQ_FIRST(&softc->rework_queue); queue = "rework"; } else { ccbh = TAILQ_FIRST(&softc->work_queue); if (ccbh == NULL) { xpt_release_ccb(iccb); return; } atio = (struct ccb_accept_tio *)ccbh; TAILQ_REMOVE(&softc->work_queue, ccbh, periph_links.tqe); more = TAILQ_FIRST(&softc->work_queue) != NULL; queue = "work"; } ATIO_PPD(atio)->on_queue = 0; if (atio->tag_id == 0xffffffff || atio->ccb_h.func_code != XPT_ACCEPT_TARGET_IO) { panic("BAD ATIO"); } data_len = is_write = 0; data_ptr = NULL; csio = &iccb->csio; status = SCSI_STATUS_OK; flags = CAM_SEND_STATUS; memset(&atio->sense_data, 0, sizeof (atio->sense_data)); cdb = atio->cdb_io.cdb_bytes; ISP_PATH_PRT(softc->isp, ISP_LOGTDEBUG0, ccbh->path, "%s: [0x%x] processing ATIO from %s queue initiator 0x%x CDB=0x%x data_offset=%u\n", __func__, atio->tag_id, queue, atio->init_id, cdb[0], ATIO_PPD(atio)->offset); return_lun = XS_LUN(atio); if (return_lun != 0) { xpt_print(atio->ccb_h.path, "[0x%x] Non-Zero Lun %d: cdb0=0x%x\n", atio->tag_id, return_lun, cdb[0]); if (cdb[0] != INQUIRY && cdb[0] != REPORT_LUNS && cdb[0] != REQUEST_SENSE) { status = SCSI_STATUS_CHECK_COND; SDFIXED(atio->sense_data)->error_code = SSD_ERRCODE_VALID|SSD_CURRENT_ERROR; SDFIXED(atio->sense_data)->flags = SSD_KEY_ILLEGAL_REQUEST; SDFIXED(atio->sense_data)->add_sense_code = 0x25; /* LOGICAL UNIT NOT SUPPORTED */ atio->sense_len = SSD_MIN_SIZE; } return_lun = CAM_LUN_WILDCARD; } switch (cdb[0]) { case REQUEST_SENSE: flags |= CAM_DIR_IN; data_len = sizeof (atio->sense_data); junk_data[0] = SSD_ERRCODE_VALID|SSD_CURRENT_ERROR|SSD_KEY_NO_SENSE; memset(junk_data+1, 0, data_len-1); if (data_len > cdb[4]) { data_len = cdb[4]; } if (data_len) { data_ptr = junk_data; } break; case WRITE_6: case WRITE_10: case WRITE_12: case WRITE_16: is_write = 1; /* FALLTHROUGH */ case READ_6: case READ_10: case READ_12: case READ_16: is_data_cmd = 1; r = isptarg_rwparm(cdb, disk_data, disk_size, ATIO_PPD(atio)->offset, &data_ptr, &data_len, &last); if (r != 0) { status = SCSI_STATUS_CHECK_COND; SDFIXED(atio->sense_data)->error_code = SSD_ERRCODE_VALID|SSD_CURRENT_ERROR; SDFIXED(atio->sense_data)->flags = SSD_KEY_ILLEGAL_REQUEST; if (r == -1) { SDFIXED(atio->sense_data)->add_sense_code = 0x21; /* LOGICAL BLOCK ADDRESS OUT OF RANGE */ } else { SDFIXED(atio->sense_data)->add_sense_code = 0x20; /* INVALID COMMAND OPERATION CODE */ } atio->sense_len = SSD_MIN_SIZE; } else { #ifdef ISP_SEPARATE_STATUS if (last && data_len) { last = 0; } #endif if (last == 0) { flags &= ~CAM_SEND_STATUS; } if (data_len) { ATIO_PPD(atio)->offset += data_len; if (is_write) flags |= CAM_DIR_OUT; else flags |= CAM_DIR_IN; } else { flags |= CAM_DIR_NONE; } } break; case INQUIRY: flags |= CAM_DIR_IN; if (cdb[1] || cdb[2] || cdb[3]) { status = SCSI_STATUS_CHECK_COND; SDFIXED(atio->sense_data)->error_code = SSD_ERRCODE_VALID|SSD_CURRENT_ERROR; SDFIXED(atio->sense_data)->flags = SSD_KEY_UNIT_ATTENTION; SDFIXED(atio->sense_data)->add_sense_code = 0x24; /* INVALID FIELD IN CDB */ atio->sense_len = SSD_MIN_SIZE; break; } data_len = sizeof (iqd); if (data_len > cdb[4]) { data_len = cdb[4]; } if (data_len) { if (XS_LUN(iccb) != 0) { memcpy(junk_data, niliqd, sizeof (iqd)); } else { memcpy(junk_data, iqd, sizeof (iqd)); } data_ptr = junk_data; } break; case TEST_UNIT_READY: flags |= CAM_DIR_NONE; if (ca) { ca = 0; status = SCSI_STATUS_CHECK_COND; SDFIXED(atio->sense_data)->error_code = SSD_ERRCODE_VALID|SSD_CURRENT_ERROR; SDFIXED(atio->sense_data)->flags = SSD_KEY_UNIT_ATTENTION; SDFIXED(atio->sense_data)->add_sense_code = 0x29; /* POWER ON, RESET, OR BUS DEVICE RESET OCCURRED */ atio->sense_len = SSD_MIN_SIZE; } break; case SYNCHRONIZE_CACHE: case START_STOP: case RESERVE: case RELEASE: case VERIFY_10: flags |= CAM_DIR_NONE; break; case READ_CAPACITY: flags |= CAM_DIR_IN; if (cdb[2] || cdb[3] || cdb[4] || cdb[5]) { status = SCSI_STATUS_CHECK_COND; SDFIXED(atio->sense_data)->error_code = SSD_ERRCODE_VALID|SSD_CURRENT_ERROR; SDFIXED(atio->sense_data)->flags = SSD_KEY_ILLEGAL_REQUEST; SDFIXED(atio->sense_data)->add_sense_code = 0x24; /* INVALID FIELD IN CDB */ atio->sense_len = SSD_MIN_SIZE; break; } if (cdb[8] & 0x1) { /* PMI */ junk_data[0] = 0xff; junk_data[1] = 0xff; junk_data[2] = 0xff; junk_data[3] = 0xff; } else { uint64_t last_blk = (disk_size >> DISK_SHIFT) - 1; if (last_blk < 0xffffffffULL) { junk_data[0] = (last_blk >> 24) & 0xff; junk_data[1] = (last_blk >> 16) & 0xff; junk_data[2] = (last_blk >> 8) & 0xff; junk_data[3] = (last_blk) & 0xff; } else { junk_data[0] = 0xff; junk_data[1] = 0xff; junk_data[2] = 0xff; junk_data[3] = 0xff; } } junk_data[4] = ((1 << DISK_SHIFT) >> 24) & 0xff; junk_data[5] = ((1 << DISK_SHIFT) >> 16) & 0xff; junk_data[6] = ((1 << DISK_SHIFT) >> 8) & 0xff; junk_data[7] = ((1 << DISK_SHIFT)) & 0xff; data_ptr = junk_data; data_len = 8; break; case REPORT_LUNS: flags |= CAM_DIR_IN; memset(junk_data, 0, JUNK_SIZE); junk_data[0] = (1 << 3) >> 24; junk_data[1] = (1 << 3) >> 16; junk_data[2] = (1 << 3) >> 8; junk_data[3] = (1 << 3); ptr = NULL; for (i = 0; i < 1; i++) { ptr = &junk_data[8 + (i << 3)]; if (i >= 256) { ptr[0] = 0x40 | ((i >> 8) & 0x3f); } ptr[1] = i; } data_ptr = junk_data; data_len = (ptr + 8) - junk_data; break; default: flags |= CAM_DIR_NONE; status = SCSI_STATUS_CHECK_COND; SDFIXED(atio->sense_data)->error_code = SSD_ERRCODE_VALID|SSD_CURRENT_ERROR; SDFIXED(atio->sense_data)->flags = SSD_KEY_ILLEGAL_REQUEST; SDFIXED(atio->sense_data)->add_sense_code = 0x20; /* INVALID COMMAND OPERATION CODE */ atio->sense_len = SSD_MIN_SIZE; break; } /* * If we are done with the transaction, tell the * controller to send status and perform a CMD_CMPLT. * If we have associated sense data, see if we can * send that too. */ if (status == SCSI_STATUS_CHECK_COND) { flags |= CAM_SEND_SENSE; csio->sense_len = atio->sense_len; csio->sense_data = atio->sense_data; flags &= ~CAM_DIR_MASK; data_len = 0; data_ptr = NULL; } cam_fill_ctio(csio, 0, isptarg_done, flags, MSG_SIMPLE_Q_TAG, atio->tag_id, atio->init_id, status, data_ptr, data_len, 30 * hz); iccb->ccb_h.target_id = atio->ccb_h.target_id; iccb->ccb_h.target_lun = return_lun; iccb->ccb_h.ccb_atio = atio; CTIO_SEQ(iccb) = ATIO_PPD(atio)->sequence++; ATIO_PPD(atio)->ctio_cnt++; if (flags & CAM_SEND_STATUS) { KASSERT((ATIO_PPD(atio)->status_sent == 0), ("we have already sent status for 0x%x in %s", atio->tag_id, __func__)); ATIO_PPD(atio)->status_sent = 1; } ISP_PATH_PRT(softc->isp, ISP_LOGTDEBUG0, atio->ccb_h.path, "%s: sending downstream for 0x%x sequence %u len %u flags %x\n", __func__, atio->tag_id, CTIO_SEQ(iccb), data_len, flags); xpt_action(iccb); if ((atio->ccb_h.status & CAM_DEV_QFRZN) != 0) { cam_release_devq(periph->path, 0, 0, 0, 0); atio->ccb_h.status &= ~CAM_DEV_QFRZN; } #ifdef ISP_MULTI_CCBS if (is_data_cmd && ATIO_PPD(atio)->status_sent == 0 && ATIO_PPD(atio)->ctio_cnt < MULTI_CCB_DATA_CNT && ATIO_PPD(atio)->on_queue == 0) { ISP_PATH_PRT(softc->isp, ISP_LOGTDEBUG0, atio->ccb_h.path, "%s: more still to do for 0x%x\n", __func__, atio->tag_id); TAILQ_INSERT_TAIL(&softc->rework_queue, &atio->ccb_h, periph_links.tqe); ATIO_PPD(atio)->on_queue = 1; more = 1; } #endif if (more) { xpt_schedule(periph, 1); } } static cam_status isptargctor(struct cam_periph *periph, void *arg) { struct isptarg_softc *softc; softc = (struct isptarg_softc *)arg; periph->softc = softc; softc->periph = periph; softc->path = periph->path; ISP_PATH_PRT(softc->isp, ISP_LOGTDEBUG1, periph->path, "%s called\n", __func__); return (CAM_REQ_CMP); } static void isptargdtor(struct cam_periph *periph) { struct isptarg_softc *softc; softc = (struct isptarg_softc *)periph->softc; ISP_PATH_PRT(softc->isp, ISP_LOGTDEBUG1, periph->path, "%s called\n", __func__); softc->periph = NULL; softc->path = NULL; periph->softc = NULL; } static void isptarg_done(struct cam_periph *periph, union ccb *ccb) { struct isptarg_softc *softc; ispsoftc_t *isp; uint32_t newoff; struct ccb_accept_tio *atio; struct ccb_immediate_notify *inot; cam_status status; softc = (struct isptarg_softc *)periph->softc; isp = softc->isp; status = ccb->ccb_h.status & CAM_STATUS_MASK; switch (ccb->ccb_h.func_code) { case XPT_ACCEPT_TARGET_IO: atio = (struct ccb_accept_tio *) ccb; ISP_PATH_PRT(isp, ISP_LOGTDEBUG0, ccb->ccb_h.path, "[0x%x] ATIO seen in %s\n", atio->tag_id, __func__); memset(ATIO_PPD(atio), 0, sizeof (ppd_t)); TAILQ_INSERT_TAIL(&softc->work_queue, &ccb->ccb_h, periph_links.tqe); ATIO_PPD(atio)->on_queue = 1; xpt_schedule(periph, 1); break; case XPT_IMMEDIATE_NOTIFY: inot = (struct ccb_immediate_notify *) ccb; ISP_PATH_PRT(isp, ISP_LOGTDEBUG0, ccb->ccb_h.path, "[0x%x] INOT for 0x%x seen in %s\n", inot->tag_id, inot->seq_id, __func__); TAILQ_INSERT_TAIL(&softc->inot_queue, &ccb->ccb_h, periph_links.tqe); xpt_schedule(periph, 1); break; case XPT_CONT_TARGET_IO: atio = ccb->ccb_h.ccb_atio; KASSERT((ATIO_PPD(atio)->ctio_cnt != 0), ("ctio zero when finishing a CTIO")); ATIO_PPD(atio)->ctio_cnt--; if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { switch (ccb->ccb_h.status & CAM_STATUS_MASK) { case CAM_MESSAGE_RECV: newoff = (ccb->csio.msg_ptr[3] << 24) | (ccb->csio.msg_ptr[4] << 16) | (ccb->csio.msg_ptr[5] << 8) | (ccb->csio.msg_ptr[6]); ISP_PATH_PRT(isp, ISP_LOGWARN, ccb->ccb_h.path, "[0x%x] got message to return to reset offset to 0x%x at sequence %u\n", atio->tag_id, newoff, CTIO_SEQ(ccb)); ATIO_PPD(atio)->offset = newoff; ATIO_PPD(atio)->status_sent = 0; if (ATIO_PPD(atio)->on_queue == 0) { TAILQ_INSERT_TAIL(&softc->rework_queue, &atio->ccb_h, periph_links.tqe); ATIO_PPD(atio)->on_queue = 1; } xpt_schedule(periph, 1); break; default: cam_error_print(ccb, CAM_ESF_ALL, CAM_EPF_ALL); xpt_action((union ccb *)atio); break; } } else if ((ccb->ccb_h.flags & CAM_SEND_STATUS) == 0) { ISP_PATH_PRT(isp, ISP_LOGTDEBUG0, ccb->ccb_h.path, "[0x%x] MID CTIO sequence %u seen in %s\n", atio->tag_id, CTIO_SEQ(ccb), __func__); if (ATIO_PPD(atio)->status_sent == 0 && ATIO_PPD(atio)->on_queue == 0) { TAILQ_INSERT_TAIL(&softc->rework_queue, &atio->ccb_h, periph_links.tqe); ATIO_PPD(atio)->on_queue = 1; } xpt_schedule(periph, 1); } else { KASSERT((ATIO_PPD(atio)->ctio_cnt == 0), ("ctio count still %d when we think we've sent the STATUS ctio", ATIO_PPD(atio)->ctio_cnt)); ISP_PATH_PRT(isp, ISP_LOGTDEBUG0, ccb->ccb_h.path, "[0x%x] FINAL CTIO sequence %u seen in %s\n", atio->tag_id, CTIO_SEQ(ccb), __func__); xpt_action((union ccb *)atio); } if ((ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { cam_release_devq(ccb->ccb_h.path, 0, 0, 0, 0); ccb->ccb_h.status &= ~CAM_DEV_QFRZN; } xpt_release_ccb(ccb); break; case XPT_NOTIFY_ACKNOWLEDGE: if ((ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { cam_release_devq(ccb->ccb_h.path, 0, 0, 0, 0); ccb->ccb_h.status &= ~CAM_DEV_QFRZN; } inot = ccb->ccb_h.ccb_inot; ISP_PATH_PRT(isp, ISP_LOGTDEBUG1, inot->ccb_h.path, "[0x%x] recycle notify for tag 0x%x\n", inot->tag_id, inot->seq_id); xpt_release_ccb(ccb); xpt_action((union ccb *)inot); break; default: xpt_print(ccb->ccb_h.path, "unexpected code 0x%x\n", ccb->ccb_h.func_code); break; } } static void isptargasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) { struct ac_contract *acp = arg; struct ac_device_changed *fc = (struct ac_device_changed *) acp->contract_data; if (code != AC_CONTRACT) { return; } xpt_print(path, "0x%016llx Port ID 0x%06x %s\n", (unsigned long long) fc->wwpn, fc->port, fc->arrived? "arrived" : "departed"); } static void isp_target_thread(ispsoftc_t *isp, int chan) { union ccb *ccb = NULL; int i; void *wchan; cam_status status; struct isptarg_softc *softc = NULL; struct cam_periph *periph = NULL, *wperiph = NULL; struct cam_path *path, *wpath; struct cam_sim *sim; if (disk_data == NULL) { disk_size = roundup2(vm_kmem_size >> 1, (1ULL << 20)); if (disk_size < (50 << 20)) { disk_size = 50 << 20; } disk_data = malloc(disk_size, M_ISPTARG, M_WAITOK | M_ZERO); if (disk_data == NULL) { isp_prt(isp, ISP_LOGERR, "%s: could not allocate disk data", __func__); goto out; } isp_prt(isp, ISP_LOGINFO, "allocated a %ju MiB disk", (uintmax_t) (disk_size >> 20)); } junk_data = malloc(JUNK_SIZE, M_ISPTARG, M_WAITOK | M_ZERO); if (junk_data == NULL) { isp_prt(isp, ISP_LOGERR, "%s: could not allocate junk", __func__); goto out; } softc = malloc(sizeof (*softc), M_ISPTARG, M_WAITOK | M_ZERO); if (softc == NULL) { isp_prt(isp, ISP_LOGERR, "%s: could not allocate softc", __func__); goto out; } TAILQ_INIT(&softc->work_queue); TAILQ_INIT(&softc->rework_queue); TAILQ_INIT(&softc->running_queue); TAILQ_INIT(&softc->inot_queue); softc->isp = isp; periphdriver_register(&isptargdriver); ISP_GET_PC(isp, chan, sim, sim); ISP_GET_PC(isp, chan, path, path); status = xpt_create_path(&wpath, NULL, cam_sim_path(sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD); if (status != CAM_REQ_CMP) { isp_prt(isp, ISP_LOGERR, "%s: could not allocate wildcard path", __func__); return; } status = xpt_create_path(&path, NULL, cam_sim_path(sim), 0, 0); if (status != CAM_REQ_CMP) { xpt_free_path(wpath); isp_prt(isp, ISP_LOGERR, "%s: could not allocate path", __func__); return; } ISP_LOCK(isp); status = cam_periph_alloc(isptargctor, NULL, isptargdtor, isptargstart, "isptarg", CAM_PERIPH_BIO, wpath, NULL, 0, softc); if (status != CAM_REQ_CMP) { ISP_UNLOCK(isp); isp_prt(isp, ISP_LOGERR, "%s: cam_periph_alloc for wildcard failed", __func__); goto out; } wperiph = cam_periph_find(wpath, "isptarg"); if (wperiph == NULL) { ISP_UNLOCK(isp); isp_prt(isp, ISP_LOGERR, "%s: wildcard periph already allocated but doesn't exist", __func__); goto out; } status = cam_periph_alloc(isptargctor, NULL, isptargdtor, isptargstart, "isptarg", CAM_PERIPH_BIO, path, NULL, 0, softc); if (status != CAM_REQ_CMP) { ISP_UNLOCK(isp); isp_prt(isp, ISP_LOGERR, "%s: cam_periph_alloc failed", __func__); goto out; } periph = cam_periph_find(path, "isptarg"); if (periph == NULL) { ISP_UNLOCK(isp); isp_prt(isp, ISP_LOGERR, "%s: periph already allocated but doesn't exist", __func__); goto out; } status = xpt_register_async(AC_CONTRACT, isptargasync, isp, wpath); if (status != CAM_REQ_CMP) { ISP_UNLOCK(isp); isp_prt(isp, ISP_LOGERR, "%s: xpt_register_async failed", __func__); goto out; } ISP_UNLOCK(isp); ccb = xpt_alloc_ccb(); /* * Make sure role is none. */ xpt_setup_ccb(&ccb->ccb_h, periph->path, 10); ccb->ccb_h.func_code = XPT_SET_SIM_KNOB; ccb->knob.xport_specific.fc.role = KNOB_ROLE_NONE; ccb->knob.xport_specific.fc.valid = KNOB_VALID_ROLE; ISP_LOCK(isp); xpt_action(ccb); ISP_UNLOCK(isp); /* * Now enable luns */ xpt_setup_ccb(&ccb->ccb_h, periph->path, 10); ccb->ccb_h.func_code = XPT_EN_LUN; ccb->cel.enable = 1; ISP_LOCK(isp); xpt_action(ccb); ISP_UNLOCK(isp); if (ccb->ccb_h.status != CAM_REQ_CMP) { xpt_free_ccb(ccb); xpt_print(periph->path, "failed to enable lun (0x%x)\n", ccb->ccb_h.status); goto out; } xpt_setup_ccb(&ccb->ccb_h, wperiph->path, 10); ccb->ccb_h.func_code = XPT_EN_LUN; ccb->cel.enable = 1; ISP_LOCK(isp); xpt_action(ccb); ISP_UNLOCK(isp); if (ccb->ccb_h.status != CAM_REQ_CMP) { xpt_free_ccb(ccb); xpt_print(wperiph->path, "failed to enable lun (0x%x)\n", ccb->ccb_h.status); goto out; } xpt_free_ccb(ccb); /* * Add resources */ ISP_GET_PC(isp, chan, target_proc, wchan); for (i = 0; i < 4; i++) { ccb = malloc(sizeof (*ccb), M_ISPTARG, M_WAITOK | M_ZERO); xpt_setup_ccb(&ccb->ccb_h, wperiph->path, 1); ccb->ccb_h.func_code = XPT_ACCEPT_TARGET_IO; ccb->ccb_h.cbfcnp = isptarg_done; ccb->ccb_h.ppriv_ptr0 = malloc(sizeof (ppd_t), M_ISPTARG, M_WAITOK | M_ZERO); ISP_LOCK(isp); xpt_action(ccb); ISP_UNLOCK(isp); } for (i = 0; i < NISP_TARG_CMDS; i++) { ccb = malloc(sizeof (*ccb), M_ISPTARG, M_WAITOK | M_ZERO); xpt_setup_ccb(&ccb->ccb_h, periph->path, 1); ccb->ccb_h.func_code = XPT_ACCEPT_TARGET_IO; ccb->ccb_h.cbfcnp = isptarg_done; ccb->ccb_h.ppriv_ptr0 = malloc(sizeof (ppd_t), M_ISPTARG, M_WAITOK | M_ZERO); ISP_LOCK(isp); xpt_action(ccb); ISP_UNLOCK(isp); } for (i = 0; i < 4; i++) { ccb = malloc(sizeof (*ccb), M_ISPTARG, M_WAITOK | M_ZERO); xpt_setup_ccb(&ccb->ccb_h, wperiph->path, 1); ccb->ccb_h.func_code = XPT_IMMEDIATE_NOTIFY; ccb->ccb_h.cbfcnp = isptarg_done; ISP_LOCK(isp); xpt_action(ccb); ISP_UNLOCK(isp); } for (i = 0; i < NISP_TARG_NOTIFIES; i++) { ccb = malloc(sizeof (*ccb), M_ISPTARG, M_WAITOK | M_ZERO); xpt_setup_ccb(&ccb->ccb_h, periph->path, 1); ccb->ccb_h.func_code = XPT_IMMEDIATE_NOTIFY; ccb->ccb_h.cbfcnp = isptarg_done; ISP_LOCK(isp); xpt_action(ccb); ISP_UNLOCK(isp); } /* * Now turn it all back on */ xpt_setup_ccb(&ccb->ccb_h, periph->path, 10); ccb->ccb_h.func_code = XPT_SET_SIM_KNOB; ccb->knob.xport_specific.fc.valid = KNOB_VALID_ROLE; ccb->knob.xport_specific.fc.role = KNOB_ROLE_TARGET; ISP_LOCK(isp); xpt_action(ccb); ISP_UNLOCK(isp); /* * Okay, while things are still active, sleep... */ ISP_LOCK(isp); for (;;) { ISP_GET_PC(isp, chan, proc_active, i); if (i == 0) { break; } msleep(wchan, &isp->isp_lock, PUSER, "tsnooze", 0); } ISP_UNLOCK(isp); out: if (wperiph) { cam_periph_invalidate(wperiph); } if (periph) { cam_periph_invalidate(periph); } if (junk_data) { free(junk_data, M_ISPTARG); } if (disk_data) { free(disk_data, M_ISPTARG); } if (softc) { free(softc, M_ISPTARG); } xpt_free_path(path); xpt_free_path(wpath); } static void isp_target_thread_pi(void *arg) { struct isp_spi *pi = arg; ispsoftc_t *isp = cam_sim_softc(pi->sim); int chan = cam_sim_bus(pi->sim); isp_target_thread(isp, chan); ISP_SPI_PC(isp, chan)->num_threads -= 1; kthread_exit(); } static void isp_target_thread_fc(void *arg) { struct isp_fc *fc = arg; ispsoftc_t *isp = cam_sim_softc(pi->sim); int chan = cam_sim_bus(pi->sim); isp_target_thread(isp, chan); ISP_FC_PC(isp, chan)->num_threads -= 1; kthread_exit(); } static int isptarg_rwparm(uint8_t *cdb, uint8_t *dp, uint64_t dl, uint32_t offset, uint8_t **kp, uint32_t *tl, int *lp) { uint32_t cnt, curcnt; uint64_t lba; switch (cdb[0]) { case WRITE_16: case READ_16: cnt = (((uint32_t)cdb[10]) << 24) | (((uint32_t)cdb[11]) << 16) | (((uint32_t)cdb[12]) << 8) | ((uint32_t)cdb[13]); lba = (((uint64_t)cdb[2]) << 56) | (((uint64_t)cdb[3]) << 48) | (((uint64_t)cdb[4]) << 40) | (((uint64_t)cdb[5]) << 32) | (((uint64_t)cdb[6]) << 24) | (((uint64_t)cdb[7]) << 16) | (((uint64_t)cdb[8]) << 8) | ((uint64_t)cdb[9]); break; case WRITE_12: case READ_12: cnt = (((uint32_t)cdb[6]) << 16) | (((uint32_t)cdb[7]) << 8) | ((u_int32_t)cdb[8]); lba = (((uint32_t)cdb[2]) << 24) | (((uint32_t)cdb[3]) << 16) | (((uint32_t)cdb[4]) << 8) | ((uint32_t)cdb[5]); break; case WRITE_10: case READ_10: cnt = (((uint32_t)cdb[7]) << 8) | ((u_int32_t)cdb[8]); lba = (((uint32_t)cdb[2]) << 24) | (((uint32_t)cdb[3]) << 16) | (((uint32_t)cdb[4]) << 8) | ((uint32_t)cdb[5]); break; case WRITE_6: case READ_6: cnt = cdb[4]; if (cnt == 0) { cnt = 256; } lba = (((uint32_t)cdb[1] & 0x1f) << 16) | (((uint32_t)cdb[2]) << 8) | ((uint32_t)cdb[3]); break; default: return (-1); } cnt <<= DISK_SHIFT; lba <<= DISK_SHIFT; if (offset == cnt) { *lp = 1; return (0); } if (lba + cnt > dl) { return (-2); } curcnt = MAX_ISP_TARG_TRANSFER; if (offset + curcnt >= cnt) { curcnt = cnt - offset; *lp = 1; } else { *lp = 0; } #ifdef ISP_MULTI_CCBS if (curcnt > MULTI_CCB_DATA_LIM) curcnt = MULTI_CCB_DATA_LIM; #endif *tl = curcnt; *kp = &dp[lba + offset]; return (0); } #endif #endif static void isp_cam_async(void *cbarg, uint32_t code, struct cam_path *path, void *arg) { struct cam_sim *sim; int bus, tgt; ispsoftc_t *isp; sim = (struct cam_sim *)cbarg; isp = (ispsoftc_t *) cam_sim_softc(sim); bus = cam_sim_bus(sim); tgt = xpt_path_target_id(path); switch (code) { case AC_LOST_DEVICE: if (IS_SCSI(isp)) { uint16_t oflags, nflags; sdparam *sdp = SDPARAM(isp, bus); if (tgt >= 0) { nflags = sdp->isp_devparam[tgt].nvrm_flags; #ifndef ISP_TARGET_MODE nflags &= DPARM_SAFE_DFLT; if (isp->isp_loaded_fw) { nflags |= DPARM_NARROW | DPARM_ASYNC; } #else nflags = DPARM_DEFAULT; #endif oflags = sdp->isp_devparam[tgt].goal_flags; sdp->isp_devparam[tgt].goal_flags = nflags; sdp->isp_devparam[tgt].dev_update = 1; sdp->update = 1; (void) isp_control(isp, ISPCTL_UPDATE_PARAMS, bus); sdp->isp_devparam[tgt].goal_flags = oflags; } } break; default: isp_prt(isp, ISP_LOGWARN, "isp_cam_async: Code 0x%x", code); break; } } static void isp_poll(struct cam_sim *sim) { ispsoftc_t *isp = cam_sim_softc(sim); uint32_t isr; uint16_t sema, mbox; if (ISP_READ_ISR(isp, &isr, &sema, &mbox)) { isp_intr(isp, isr, sema, mbox); } } static void isp_watchdog(void *arg) { struct ccb_scsiio *xs = arg; ispsoftc_t *isp; uint32_t ohandle = ISP_HANDLE_FREE, handle; isp = XS_ISP(xs); handle = isp_find_handle(isp, xs); /* * Hand crank the interrupt code just to be sure the command isn't stuck somewhere. */ if (handle != ISP_HANDLE_FREE) { uint32_t isr; uint16_t sema, mbox; if (ISP_READ_ISR(isp, &isr, &sema, &mbox) != 0) { isp_intr(isp, isr, sema, mbox); } ohandle = handle; handle = isp_find_handle(isp, xs); } if (handle != ISP_HANDLE_FREE) { /* * Try and make sure the command is really dead before * we release the handle (and DMA resources) for reuse. * * If we are successful in aborting the command then * we're done here because we'll get the command returned * back separately. */ if (isp_control(isp, ISPCTL_ABORT_CMD, xs) == 0) { return; } /* * Note that after calling the above, the command may in * fact have been completed. */ xs = isp_find_xs(isp, handle); /* * If the command no longer exists, then we won't * be able to find the xs again with this handle. */ if (xs == NULL) { return; } /* * After this point, the command is really dead. */ if (XS_XFRLEN(xs)) { ISP_DMAFREE(isp, xs, handle); } isp_destroy_handle(isp, handle); isp_prt(isp, ISP_LOGERR, "%s: timeout for handle 0x%x", __func__, handle); xs->ccb_h.status &= ~CAM_STATUS_MASK; xs->ccb_h.status |= CAM_CMD_TIMEOUT; isp_prt_endcmd(isp, xs); isp_done(xs); } else { if (ohandle != ISP_HANDLE_FREE) { isp_prt(isp, ISP_LOGWARN, "%s: timeout for handle 0x%x, recovered during interrupt", __func__, ohandle); } else { isp_prt(isp, ISP_LOGWARN, "%s: timeout for handle already free", __func__); } } } static void isp_make_here(ispsoftc_t *isp, int chan, int tgt) { union ccb *ccb; struct isp_fc *fc = ISP_FC_PC(isp, chan); if (isp_autoconfig == 0) { return; } /* * Allocate a CCB, create a wildcard path for this target and schedule a rescan. */ ccb = xpt_alloc_ccb_nowait(); if (ccb == NULL) { isp_prt(isp, ISP_LOGWARN, "Chan %d unable to alloc CCB for rescan", chan); return; } if (xpt_create_path(&ccb->ccb_h.path, NULL, cam_sim_path(fc->sim), tgt, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { isp_prt(isp, ISP_LOGWARN, "unable to create path for rescan"); xpt_free_ccb(ccb); return; } xpt_rescan(ccb); } static void isp_make_gone(ispsoftc_t *isp, int chan, int tgt) { struct cam_path *tp; struct isp_fc *fc = ISP_FC_PC(isp, chan); if (isp_autoconfig == 0) { return; } if (xpt_create_path(&tp, NULL, cam_sim_path(fc->sim), tgt, CAM_LUN_WILDCARD) == CAM_REQ_CMP) { xpt_async(AC_LOST_DEVICE, tp, NULL); xpt_free_path(tp); } } /* * Gone Device Timer Function- when we have decided that a device has gone * away, we wait a specific period of time prior to telling the OS it has * gone away. * * This timer function fires once a second and then scans the port database * for devices that are marked dead but still have a virtual target assigned. * We decrement a counter for that port database entry, and when it hits zero, * we tell the OS the device has gone away. */ static void isp_gdt(void *arg) { struct isp_fc *fc = arg; taskqueue_enqueue(taskqueue_thread, &fc->gtask); } static void isp_gdt_task(void *arg, int pending) { struct isp_fc *fc = arg; ispsoftc_t *isp = fc->isp; int chan = fc - isp->isp_osinfo.pc.fc; fcportdb_t *lp; int dbidx, tgt, more_to_do = 0; ISP_LOCK(isp); isp_prt(isp, ISP_LOGDEBUG0, "Chan %d GDT timer expired", chan); for (dbidx = 0; dbidx < MAX_FC_TARG; dbidx++) { lp = &FCPARAM(isp, chan)->portdb[dbidx]; if (lp->state != FC_PORTDB_STATE_ZOMBIE) { continue; } if (lp->dev_map_idx == 0 || lp->target_mode) { continue; } if (lp->gone_timer != 0) { isp_prt(isp, ISP_LOG_SANCFG, "%s: Chan %d more to do for target %u (timer=%u)", __func__, chan, lp->dev_map_idx - 1, lp->gone_timer); lp->gone_timer -= 1; more_to_do++; continue; } tgt = lp->dev_map_idx - 1; FCPARAM(isp, chan)->isp_dev_map[tgt] = 0; lp->dev_map_idx = 0; lp->state = FC_PORTDB_STATE_NIL; isp_prt(isp, ISP_LOGCONFIG, prom3, chan, lp->portid, tgt, "Gone Device Timeout"); isp_make_gone(isp, chan, tgt); } if (fc->ready) { if (more_to_do) { callout_reset(&fc->gdt, hz, isp_gdt, fc); } else { callout_deactivate(&fc->gdt); isp_prt(isp, ISP_LOG_SANCFG, "Chan %d Stopping Gone Device Timer @ %lu", chan, (unsigned long) time_uptime); } } ISP_UNLOCK(isp); } /* * Loop Down Timer Function- when loop goes down, a timer is started and * and after it expires we come here and take all probational devices that * the OS knows about and the tell the OS that they've gone away. * * We don't clear the devices out of our port database because, when loop * come back up, we have to do some actual cleanup with the chip at that * point (implicit PLOGO, e.g., to get the chip's port database state right). */ static void isp_ldt(void *arg) { struct isp_fc *fc = arg; taskqueue_enqueue(taskqueue_thread, &fc->ltask); } static void isp_ldt_task(void *arg, int pending) { struct isp_fc *fc = arg; ispsoftc_t *isp = fc->isp; int chan = fc - isp->isp_osinfo.pc.fc; fcportdb_t *lp; int dbidx, tgt, i; ISP_LOCK(isp); isp_prt(isp, ISP_LOG_SANCFG|ISP_LOGDEBUG0, "Chan %d Loop Down Timer expired @ %lu", chan, (unsigned long) time_uptime); callout_deactivate(&fc->ldt); /* * Notify to the OS all targets who we now consider have departed. */ for (dbidx = 0; dbidx < MAX_FC_TARG; dbidx++) { lp = &FCPARAM(isp, chan)->portdb[dbidx]; if (lp->state != FC_PORTDB_STATE_PROBATIONAL) { continue; } if (lp->dev_map_idx == 0 || lp->target_mode) { continue; } /* * XXX: CLEAN UP AND COMPLETE ANY PENDING COMMANDS FIRST! */ for (i = 0; i < isp->isp_maxcmds; i++) { struct ccb_scsiio *xs; if (!ISP_VALID_HANDLE(isp, isp->isp_xflist[i].handle)) { continue; } if ((xs = isp->isp_xflist[i].cmd) == NULL) { continue; } if (dbidx != (FCPARAM(isp, chan)->isp_dev_map[XS_TGT(xs)] - 1)) { continue; } isp_prt(isp, ISP_LOGWARN, "command handle 0x%x for %d.%d.%d orphaned by loop down timeout", isp->isp_xflist[i].handle, chan, XS_TGT(xs), XS_LUN(xs)); } /* * Mark that we've announced that this device is gone.... */ lp->announced = 1; /* * but *don't* change the state of the entry. Just clear * any target id stuff and announce to CAM that the * device is gone. This way any necessary PLOGO stuff * will happen when loop comes back up. */ tgt = lp->dev_map_idx - 1; FCPARAM(isp, chan)->isp_dev_map[tgt] = 0; lp->dev_map_idx = 0; lp->state = FC_PORTDB_STATE_NIL; isp_prt(isp, ISP_LOGCONFIG, prom3, chan, lp->portid, tgt, "Loop Down Timeout"); isp_make_gone(isp, chan, tgt); } if (FCPARAM(isp, chan)->role & ISP_ROLE_INITIATOR) { isp_unfreeze_loopdown(isp, chan); } /* * The loop down timer has expired. Wake up the kthread * to notice that fact (or make it false). */ fc->loop_dead = 1; fc->loop_down_time = fc->loop_down_limit+1; wakeup(fc); ISP_UNLOCK(isp); } static void isp_kthread(void *arg) { struct isp_fc *fc = arg; ispsoftc_t *isp = fc->isp; int chan = fc - isp->isp_osinfo.pc.fc; int slp = 0; mtx_lock(&isp->isp_osinfo.lock); while (isp->isp_osinfo.is_exiting == 0) { int lb, lim; isp_prt(isp, ISP_LOG_SANCFG|ISP_LOGDEBUG0, "%s: Chan %d checking FC state", __func__, chan); lb = isp_fc_runstate(isp, chan, 250000); /* * Our action is different based upon whether we're supporting * Initiator mode or not. If we are, we might freeze the simq * when loop is down and set all sorts of different delays to * check again. * * If not, we simply just wait for loop to come up. */ if (lb && (FCPARAM(isp, chan)->role & ISP_ROLE_INITIATOR)) { /* * Increment loop down time by the last sleep interval */ fc->loop_down_time += slp; if (lb < 0) { isp_prt(isp, ISP_LOG_SANCFG|ISP_LOGDEBUG0, "%s: Chan %d FC loop not up (down count %d)", __func__, chan, fc->loop_down_time); } else { isp_prt(isp, ISP_LOG_SANCFG|ISP_LOGDEBUG0, "%s: Chan %d FC got to %d (down count %d)", __func__, chan, lb, fc->loop_down_time); } /* * If we've never seen loop up and we've waited longer * than quickboot time, or we've seen loop up but we've * waited longer than loop_down_limit, give up and go * to sleep until loop comes up. */ if (FCPARAM(isp, chan)->loop_seen_once == 0) { lim = isp_quickboot_time; } else { lim = fc->loop_down_limit; } if (fc->loop_down_time >= lim) { isp_freeze_loopdown(isp, chan, "loop limit hit"); slp = 0; } else if (fc->loop_down_time < 10) { slp = 1; } else if (fc->loop_down_time < 30) { slp = 5; } else if (fc->loop_down_time < 60) { slp = 10; } else if (fc->loop_down_time < 120) { slp = 20; } else { slp = 30; } } else if (lb) { isp_prt(isp, ISP_LOG_SANCFG|ISP_LOGDEBUG0, "%s: Chan %d FC Loop Down", __func__, chan); fc->loop_down_time += slp; if (fc->loop_down_time > 300) slp = 0; else slp = 60; } else { isp_prt(isp, ISP_LOG_SANCFG|ISP_LOGDEBUG0, "%s: Chan %d FC state OK", __func__, chan); fc->loop_down_time = 0; slp = 0; } /* * If this is past the first loop up or the loop is dead and if we'd frozen the simq, unfreeze it * now so that CAM can start sending us commands. * * If the FC state isn't okay yet, they'll hit that in isp_start which will freeze the queue again * or kill the commands, as appropriate. */ if (FCPARAM(isp, chan)->loop_seen_once || fc->loop_dead) { isp_unfreeze_loopdown(isp, chan); } isp_prt(isp, ISP_LOG_SANCFG|ISP_LOGDEBUG0, "%s: Chan %d sleep time %d", __func__, chan, slp); msleep(fc, &isp->isp_osinfo.lock, PRIBIO, "ispf", slp * hz); /* * If slp is zero, we're waking up for the first time after * things have been okay. In this case, we set a deferral state * for all commands and delay hysteresis seconds before starting * the FC state evaluation. This gives the loop/fabric a chance * to settle. */ if (slp == 0 && fc->hysteresis) { isp_prt(isp, ISP_LOG_SANCFG|ISP_LOGDEBUG0, "%s: Chan %d sleep hysteresis ticks %d", __func__, chan, fc->hysteresis * hz); mtx_unlock(&isp->isp_osinfo.lock); pause("ispt", fc->hysteresis * hz); mtx_lock(&isp->isp_osinfo.lock); } } fc->num_threads -= 1; mtx_unlock(&isp->isp_osinfo.lock); kthread_exit(); } static void isp_action(struct cam_sim *sim, union ccb *ccb) { int bus, tgt, ts, error, lim; ispsoftc_t *isp; struct ccb_trans_settings *cts; CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("isp_action\n")); isp = (ispsoftc_t *)cam_sim_softc(sim); mtx_assert(&isp->isp_lock, MA_OWNED); if (isp->isp_state != ISP_RUNSTATE && ccb->ccb_h.func_code == XPT_SCSI_IO) { isp_init(isp); if (isp->isp_state != ISP_INITSTATE) { /* * Lie. Say it was a selection timeout. */ ccb->ccb_h.status = CAM_SEL_TIMEOUT | CAM_DEV_QFRZN; xpt_freeze_devq(ccb->ccb_h.path, 1); xpt_done(ccb); return; } isp->isp_state = ISP_RUNSTATE; } isp_prt(isp, ISP_LOGDEBUG2, "isp_action code %x", ccb->ccb_h.func_code); ISP_PCMD(ccb) = NULL; switch (ccb->ccb_h.func_code) { case XPT_SCSI_IO: /* Execute the requested I/O operation */ bus = XS_CHANNEL(ccb); /* * Do a couple of preliminary checks... */ if ((ccb->ccb_h.flags & CAM_CDB_POINTER) != 0) { if ((ccb->ccb_h.flags & CAM_CDB_PHYS) != 0) { ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; } } ccb->csio.req_map = NULL; #ifdef DIAGNOSTIC if (ccb->ccb_h.target_id > (ISP_MAX_TARGETS(isp) - 1)) { xpt_print(ccb->ccb_h.path, "invalid target\n"); ccb->ccb_h.status = CAM_PATH_INVALID; } else if (ccb->ccb_h.target_lun > (ISP_MAX_LUNS(isp) - 1)) { xpt_print(ccb->ccb_h.path, "invalid lun\n"); ccb->ccb_h.status = CAM_PATH_INVALID; } if (ccb->ccb_h.status == CAM_PATH_INVALID) { xpt_done(ccb); break; } #endif ccb->csio.scsi_status = SCSI_STATUS_OK; if (isp_get_pcmd(isp, ccb)) { isp_prt(isp, ISP_LOGWARN, "out of PCMDs"); cam_freeze_devq(ccb->ccb_h.path); cam_release_devq(ccb->ccb_h.path, RELSIM_RELEASE_AFTER_TIMEOUT, 0, 250, 0); xpt_done(ccb); break; } error = isp_start((XS_T *) ccb); switch (error) { case CMD_QUEUED: ccb->ccb_h.status |= CAM_SIM_QUEUED; if (ccb->ccb_h.timeout == CAM_TIME_INFINITY) { break; } ts = ccb->ccb_h.timeout; if (ts == CAM_TIME_DEFAULT) { ts = 60*1000; } ts = isp_mstohz(ts); callout_reset(&PISP_PCMD(ccb)->wdog, ts, isp_watchdog, ccb); break; case CMD_RQLATER: /* * We get this result for FC devices if the loop state isn't ready yet * or if the device in question has gone zombie on us. * * If we've never seen Loop UP at all, we requeue this request and wait * for the initial loop up delay to expire. */ lim = ISP_FC_PC(isp, bus)->loop_down_limit; if (FCPARAM(isp, bus)->loop_seen_once == 0 || ISP_FC_PC(isp, bus)->loop_down_time >= lim) { if (FCPARAM(isp, bus)->loop_seen_once == 0) { isp_prt(isp, ISP_LOGDEBUG0, "%d.%d loop not seen yet @ %lu", XS_TGT(ccb), XS_LUN(ccb), (unsigned long) time_uptime); } else { isp_prt(isp, ISP_LOGDEBUG0, "%d.%d downtime (%d) > lim (%d)", XS_TGT(ccb), XS_LUN(ccb), ISP_FC_PC(isp, bus)->loop_down_time, lim); } ccb->ccb_h.status = CAM_SEL_TIMEOUT|CAM_DEV_QFRZN; xpt_freeze_devq(ccb->ccb_h.path, 1); isp_free_pcmd(isp, ccb); xpt_done(ccb); break; } isp_prt(isp, ISP_LOGDEBUG0, "%d.%d retry later", XS_TGT(ccb), XS_LUN(ccb)); cam_freeze_devq(ccb->ccb_h.path); cam_release_devq(ccb->ccb_h.path, RELSIM_RELEASE_AFTER_TIMEOUT, 0, 1000, 0); ccb->ccb_h.status = CAM_REQUEUE_REQ; isp_free_pcmd(isp, ccb); xpt_done(ccb); break; case CMD_EAGAIN: isp_free_pcmd(isp, ccb); cam_freeze_devq(ccb->ccb_h.path); cam_release_devq(ccb->ccb_h.path, RELSIM_RELEASE_AFTER_TIMEOUT, 0, 100, 0); ccb->ccb_h.status = CAM_REQUEUE_REQ; xpt_done(ccb); break; case CMD_COMPLETE: isp_done((struct ccb_scsiio *) ccb); break; default: isp_prt(isp, ISP_LOGERR, "What's this? 0x%x at %d in file %s", error, __LINE__, __FILE__); ccb->ccb_h.status = CAM_REQUEUE_REQ; isp_free_pcmd(isp, ccb); xpt_done(ccb); } break; #ifdef ISP_TARGET_MODE case XPT_EN_LUN: /* Enable/Disable LUN as a target */ if (ccb->cel.enable) { isp_enable_lun(isp, ccb); } else { isp_disable_lun(isp, ccb); } break; case XPT_IMMED_NOTIFY: case XPT_IMMEDIATE_NOTIFY: /* Add Immediate Notify Resource */ case XPT_ACCEPT_TARGET_IO: /* Add Accept Target IO Resource */ { tstate_t *tptr = get_lun_statep(isp, XS_CHANNEL(ccb), ccb->ccb_h.target_lun); if (tptr == NULL) { tptr = get_lun_statep(isp, XS_CHANNEL(ccb), CAM_LUN_WILDCARD); } if (tptr == NULL) { const char *str; uint32_t tag; if (ccb->ccb_h.func_code == XPT_IMMEDIATE_NOTIFY) { str = "XPT_IMMEDIATE_NOTIFY"; tag = ccb->cin1.seq_id; } else { tag = ccb->atio.tag_id; str = "XPT_ACCEPT_TARGET_IO"; } ISP_PATH_PRT(isp, ISP_LOGWARN, ccb->ccb_h.path, "%s: [0x%x] no state pointer found for %s\n", __func__, tag, str); dump_tstates(isp, XS_CHANNEL(ccb)); ccb->ccb_h.status = CAM_DEV_NOT_THERE; break; } ccb->ccb_h.spriv_field0 = 0; ccb->ccb_h.spriv_ptr1 = isp; if (ccb->ccb_h.func_code == XPT_ACCEPT_TARGET_IO) { if (ccb->atio.tag_id) { atio_private_data_t *atp = isp_find_atpd(isp, tptr, ccb->atio.tag_id); if (atp) { isp_put_atpd(isp, tptr, atp); } } tptr->atio_count++; SLIST_INSERT_HEAD(&tptr->atios, &ccb->ccb_h, sim_links.sle); ISP_PATH_PRT(isp, ISP_LOGTDEBUG2, ccb->ccb_h.path, "Put FREE ATIO (tag id 0x%x), count now %d\n", ccb->atio.tag_id, tptr->atio_count); ccb->atio.tag_id = 0; } else if (ccb->ccb_h.func_code == XPT_IMMEDIATE_NOTIFY) { if (ccb->cin1.tag_id) { inot_private_data_t *ntp = isp_find_ntpd(isp, tptr, ccb->cin1.tag_id, ccb->cin1.seq_id); if (ntp) { isp_put_ntpd(isp, tptr, ntp); } } tptr->inot_count++; SLIST_INSERT_HEAD(&tptr->inots, &ccb->ccb_h, sim_links.sle); ISP_PATH_PRT(isp, ISP_LOGTDEBUG2, ccb->ccb_h.path, "Put FREE INOT, (seq id 0x%x) count now %d\n", ccb->cin1.seq_id, tptr->inot_count); ccb->cin1.seq_id = 0; } else if (ccb->ccb_h.func_code == XPT_IMMED_NOTIFY) { tptr->inot_count++; SLIST_INSERT_HEAD(&tptr->inots, &ccb->ccb_h, sim_links.sle); ISP_PATH_PRT(isp, ISP_LOGTDEBUG2, ccb->ccb_h.path, "Put FREE INOT, (seq id 0x%x) count now %d\n", ccb->cin1.seq_id, tptr->inot_count); ccb->cin1.seq_id = 0; } rls_lun_statep(isp, tptr); ccb->ccb_h.status = CAM_REQ_INPROG; break; } case XPT_NOTIFY_ACK: ccb->ccb_h.status = CAM_REQ_CMP_ERR; break; case XPT_NOTIFY_ACKNOWLEDGE: /* notify ack */ { tstate_t *tptr; inot_private_data_t *ntp; /* * XXX: Because we cannot guarantee that the path information in the notify acknowledge ccb * XXX: matches that for the immediate notify, we have to *search* for the notify structure */ /* * All the relevant path information is in the associated immediate notify */ ISP_PATH_PRT(isp, ISP_LOGTDEBUG0, ccb->ccb_h.path, "%s: [0x%x] NOTIFY ACKNOWLEDGE for 0x%x seen\n", __func__, ccb->cna2.tag_id, ccb->cna2.seq_id); ntp = get_ntp_from_tagdata(isp, ccb->cna2.tag_id, ccb->cna2.seq_id, &tptr); if (ntp == NULL) { ISP_PATH_PRT(isp, ISP_LOGWARN, ccb->ccb_h.path, "%s: [0x%x] XPT_NOTIFY_ACKNOWLEDGE of 0x%x cannot find ntp private data\n", __func__, ccb->cna2.tag_id, ccb->cna2.seq_id); ccb->ccb_h.status = CAM_DEV_NOT_THERE; xpt_done(ccb); break; } if (isp_handle_platform_target_notify_ack(isp, &ntp->rd.nt)) { rls_lun_statep(isp, tptr); cam_freeze_devq(ccb->ccb_h.path); cam_release_devq(ccb->ccb_h.path, RELSIM_RELEASE_AFTER_TIMEOUT, 0, 1000, 0); ccb->ccb_h.status &= ~CAM_STATUS_MASK; ccb->ccb_h.status |= CAM_REQUEUE_REQ; break; } isp_put_ntpd(isp, tptr, ntp); rls_lun_statep(isp, tptr); ccb->ccb_h.status = CAM_REQ_CMP; ISP_PATH_PRT(isp, ISP_LOGTDEBUG0, ccb->ccb_h.path, "%s: [0x%x] calling xpt_done for tag 0x%x\n", __func__, ccb->cna2.tag_id, ccb->cna2.seq_id); xpt_done(ccb); break; } case XPT_CONT_TARGET_IO: isp_target_start_ctio(isp, ccb, FROM_CAM); break; #endif case XPT_RESET_DEV: /* BDR the specified SCSI device */ bus = cam_sim_bus(xpt_path_sim(ccb->ccb_h.path)); tgt = ccb->ccb_h.target_id; tgt |= (bus << 16); error = isp_control(isp, ISPCTL_RESET_DEV, bus, tgt); if (error) { ccb->ccb_h.status = CAM_REQ_CMP_ERR; } else { ccb->ccb_h.status = CAM_REQ_CMP; } xpt_done(ccb); break; case XPT_ABORT: /* Abort the specified CCB */ { union ccb *accb = ccb->cab.abort_ccb; switch (accb->ccb_h.func_code) { #ifdef ISP_TARGET_MODE case XPT_ACCEPT_TARGET_IO: isp_target_mark_aborted(isp, ccb); break; #endif case XPT_SCSI_IO: error = isp_control(isp, ISPCTL_ABORT_CMD, accb); if (error) { ccb->ccb_h.status = CAM_UA_ABORT; } else { ccb->ccb_h.status = CAM_REQ_CMP; } break; default: ccb->ccb_h.status = CAM_REQ_INVALID; break; } /* * This is not a queued CCB, so the caller expects it to be * complete when control is returned. */ break; } #define IS_CURRENT_SETTINGS(c) (c->type == CTS_TYPE_CURRENT_SETTINGS) case XPT_SET_TRAN_SETTINGS: /* Nexus Settings */ cts = &ccb->cts; if (!IS_CURRENT_SETTINGS(cts)) { ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; } tgt = cts->ccb_h.target_id; bus = cam_sim_bus(xpt_path_sim(cts->ccb_h.path)); if (IS_SCSI(isp)) { struct ccb_trans_settings_scsi *scsi = &cts->proto_specific.scsi; struct ccb_trans_settings_spi *spi = &cts->xport_specific.spi; sdparam *sdp = SDPARAM(isp, bus); uint16_t *dptr; if (spi->valid == 0 && scsi->valid == 0) { ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } /* * We always update (internally) from goal_flags * so any request to change settings just gets * vectored to that location. */ dptr = &sdp->isp_devparam[tgt].goal_flags; if ((spi->valid & CTS_SPI_VALID_DISC) != 0) { if ((spi->flags & CTS_SPI_FLAGS_DISC_ENB) != 0) *dptr |= DPARM_DISC; else *dptr &= ~DPARM_DISC; } if ((scsi->valid & CTS_SCSI_VALID_TQ) != 0) { if ((scsi->flags & CTS_SCSI_FLAGS_TAG_ENB) != 0) *dptr |= DPARM_TQING; else *dptr &= ~DPARM_TQING; } if ((spi->valid & CTS_SPI_VALID_BUS_WIDTH) != 0) { if (spi->bus_width == MSG_EXT_WDTR_BUS_16_BIT) *dptr |= DPARM_WIDE; else *dptr &= ~DPARM_WIDE; } /* * XXX: FIX ME */ if ((spi->valid & CTS_SPI_VALID_SYNC_OFFSET) && (spi->valid & CTS_SPI_VALID_SYNC_RATE) && (spi->sync_period && spi->sync_offset)) { *dptr |= DPARM_SYNC; /* * XXX: CHECK FOR LEGALITY */ sdp->isp_devparam[tgt].goal_period = spi->sync_period; sdp->isp_devparam[tgt].goal_offset = spi->sync_offset; } else { *dptr &= ~DPARM_SYNC; } isp_prt(isp, ISP_LOGDEBUG0, "SET (%d.%d.%jx) to flags %x off %x per %x", bus, tgt, (uintmax_t)cts->ccb_h.target_lun, sdp->isp_devparam[tgt].goal_flags, sdp->isp_devparam[tgt].goal_offset, sdp->isp_devparam[tgt].goal_period); sdp->isp_devparam[tgt].dev_update = 1; sdp->update = 1; } ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; case XPT_GET_TRAN_SETTINGS: cts = &ccb->cts; tgt = cts->ccb_h.target_id; bus = cam_sim_bus(xpt_path_sim(cts->ccb_h.path)); if (IS_FC(isp)) { fcparam *fcp = FCPARAM(isp, bus); struct ccb_trans_settings_scsi *scsi = &cts->proto_specific.scsi; struct ccb_trans_settings_fc *fc = &cts->xport_specific.fc; unsigned int hdlidx; cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_2; cts->transport = XPORT_FC; cts->transport_version = 0; scsi->valid = CTS_SCSI_VALID_TQ; scsi->flags = CTS_SCSI_FLAGS_TAG_ENB; fc->valid = CTS_FC_VALID_SPEED; fc->bitrate = 100000; fc->bitrate *= fcp->isp_gbspeed; hdlidx = fcp->isp_dev_map[tgt] - 1; if (hdlidx < MAX_FC_TARG) { fcportdb_t *lp = &fcp->portdb[hdlidx]; fc->wwnn = lp->node_wwn; fc->wwpn = lp->port_wwn; fc->port = lp->portid; fc->valid |= CTS_FC_VALID_WWNN | CTS_FC_VALID_WWPN | CTS_FC_VALID_PORT; } } else { struct ccb_trans_settings_scsi *scsi = &cts->proto_specific.scsi; struct ccb_trans_settings_spi *spi = &cts->xport_specific.spi; sdparam *sdp = SDPARAM(isp, bus); uint16_t dval, pval, oval; if (IS_CURRENT_SETTINGS(cts)) { sdp->isp_devparam[tgt].dev_refresh = 1; sdp->update = 1; (void) isp_control(isp, ISPCTL_UPDATE_PARAMS, bus); dval = sdp->isp_devparam[tgt].actv_flags; oval = sdp->isp_devparam[tgt].actv_offset; pval = sdp->isp_devparam[tgt].actv_period; } else { dval = sdp->isp_devparam[tgt].nvrm_flags; oval = sdp->isp_devparam[tgt].nvrm_offset; pval = sdp->isp_devparam[tgt].nvrm_period; } cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_2; cts->transport = XPORT_SPI; cts->transport_version = 2; spi->valid = 0; scsi->valid = 0; spi->flags = 0; scsi->flags = 0; if (dval & DPARM_DISC) { spi->flags |= CTS_SPI_FLAGS_DISC_ENB; } if ((dval & DPARM_SYNC) && oval && pval) { spi->sync_offset = oval; spi->sync_period = pval; } else { spi->sync_offset = 0; spi->sync_period = 0; } spi->valid |= CTS_SPI_VALID_SYNC_OFFSET; spi->valid |= CTS_SPI_VALID_SYNC_RATE; spi->valid |= CTS_SPI_VALID_BUS_WIDTH; if (dval & DPARM_WIDE) { spi->bus_width = MSG_EXT_WDTR_BUS_16_BIT; } else { spi->bus_width = MSG_EXT_WDTR_BUS_8_BIT; } if (cts->ccb_h.target_lun != CAM_LUN_WILDCARD) { scsi->valid = CTS_SCSI_VALID_TQ; if (dval & DPARM_TQING) { scsi->flags |= CTS_SCSI_FLAGS_TAG_ENB; } spi->valid |= CTS_SPI_VALID_DISC; } isp_prt(isp, ISP_LOGDEBUG0, "GET %s (%d.%d.%jx) to flags %x off %x per %x", IS_CURRENT_SETTINGS(cts)? "ACTIVE" : "NVRAM", bus, tgt, (uintmax_t)cts->ccb_h.target_lun, dval, oval, pval); } ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; case XPT_CALC_GEOMETRY: cam_calc_geometry(&ccb->ccg, 1); xpt_done(ccb); break; case XPT_RESET_BUS: /* Reset the specified bus */ bus = cam_sim_bus(sim); error = isp_control(isp, ISPCTL_RESET_BUS, bus); if (error) { ccb->ccb_h.status = CAM_REQ_CMP_ERR; xpt_done(ccb); break; } if (bootverbose) { xpt_print(ccb->ccb_h.path, "reset bus on channel %d\n", bus); } if (IS_FC(isp)) { xpt_async(AC_BUS_RESET, ISP_FC_PC(isp, bus)->path, 0); } else { xpt_async(AC_BUS_RESET, ISP_SPI_PC(isp, bus)->path, 0); } ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; case XPT_TERM_IO: /* Terminate the I/O process */ ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; case XPT_SET_SIM_KNOB: /* Set SIM knobs */ { struct ccb_sim_knob *kp = &ccb->knob; fcparam *fcp; if (!IS_FC(isp)) { ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; } bus = cam_sim_bus(xpt_path_sim(kp->ccb_h.path)); fcp = FCPARAM(isp, bus); if (kp->xport_specific.fc.valid & KNOB_VALID_ADDRESS) { fcp->isp_wwnn = ISP_FC_PC(isp, bus)->def_wwnn = kp->xport_specific.fc.wwnn; fcp->isp_wwpn = ISP_FC_PC(isp, bus)->def_wwpn = kp->xport_specific.fc.wwpn; isp_prt(isp, ISP_LOGALL, "Setting Channel %d wwns to 0x%jx 0x%jx", bus, fcp->isp_wwnn, fcp->isp_wwpn); } ccb->ccb_h.status = CAM_REQ_CMP; if (kp->xport_specific.fc.valid & KNOB_VALID_ROLE) { int rchange = 0; int newrole = 0; switch (kp->xport_specific.fc.role) { case KNOB_ROLE_NONE: if (fcp->role != ISP_ROLE_NONE) { rchange = 1; newrole = ISP_ROLE_NONE; } break; case KNOB_ROLE_TARGET: if (fcp->role != ISP_ROLE_TARGET) { rchange = 1; newrole = ISP_ROLE_TARGET; } break; case KNOB_ROLE_INITIATOR: if (fcp->role != ISP_ROLE_INITIATOR) { rchange = 1; newrole = ISP_ROLE_INITIATOR; } break; case KNOB_ROLE_BOTH: #if 0 if (fcp->role != ISP_ROLE_BOTH) { rchange = 1; newrole = ISP_ROLE_BOTH; } #else /* * We don't really support dual role at present on FC cards. * * We should, but a bunch of things are currently broken, * so don't allow it. */ isp_prt(isp, ISP_LOGERR, "cannot support dual role at present"); ccb->ccb_h.status = CAM_REQ_INVALID; #endif break; } if (rchange) { ISP_PATH_PRT(isp, ISP_LOGCONFIG, ccb->ccb_h.path, "changing role on from %d to %d\n", fcp->role, newrole); #ifdef ISP_TARGET_MODE ISP_SET_PC(isp, bus, tm_enabled, 0); ISP_SET_PC(isp, bus, tm_luns_enabled, 0); #endif if (isp_fc_change_role(isp, bus, newrole) != 0) { ccb->ccb_h.status = CAM_REQ_CMP_ERR; xpt_done(ccb); break; } #ifdef ISP_TARGET_MODE if (newrole == ISP_ROLE_TARGET || newrole == ISP_ROLE_BOTH) { /* * Give the new role a chance to complain and settle */ msleep(isp, &isp->isp_lock, PRIBIO, "taking a breather", 2); ccb->ccb_h.status = isp_enable_deferred_luns(isp, bus); } #endif } } xpt_done(ccb); break; } case XPT_GET_SIM_KNOB: /* Get SIM knobs */ { struct ccb_sim_knob *kp = &ccb->knob; if (IS_FC(isp)) { fcparam *fcp; bus = cam_sim_bus(xpt_path_sim(kp->ccb_h.path)); fcp = FCPARAM(isp, bus); kp->xport_specific.fc.wwnn = fcp->isp_wwnn; kp->xport_specific.fc.wwpn = fcp->isp_wwpn; switch (fcp->role) { case ISP_ROLE_NONE: kp->xport_specific.fc.role = KNOB_ROLE_NONE; break; case ISP_ROLE_TARGET: kp->xport_specific.fc.role = KNOB_ROLE_TARGET; break; case ISP_ROLE_INITIATOR: kp->xport_specific.fc.role = KNOB_ROLE_INITIATOR; break; case ISP_ROLE_BOTH: kp->xport_specific.fc.role = KNOB_ROLE_BOTH; break; } kp->xport_specific.fc.valid = KNOB_VALID_ADDRESS | KNOB_VALID_ROLE; ccb->ccb_h.status = CAM_REQ_CMP; } else { ccb->ccb_h.status = CAM_REQ_INVALID; } xpt_done(ccb); break; } case XPT_PATH_INQ: /* Path routing inquiry */ { struct ccb_pathinq *cpi = &ccb->cpi; cpi->version_num = 1; #ifdef ISP_TARGET_MODE cpi->target_sprt = PIT_PROCESSOR | PIT_DISCONNECT | PIT_TERM_IO; #else cpi->target_sprt = 0; #endif cpi->hba_eng_cnt = 0; cpi->max_target = ISP_MAX_TARGETS(isp) - 1; cpi->max_lun = ISP_MAX_LUNS(isp) - 1; cpi->bus_id = cam_sim_bus(sim); if (isp->isp_osinfo.sixtyfourbit) cpi->maxio = (ISP_NSEG64_MAX - 1) * PAGE_SIZE; else cpi->maxio = (ISP_NSEG_MAX - 1) * PAGE_SIZE; bus = cam_sim_bus(xpt_path_sim(cpi->ccb_h.path)); if (IS_FC(isp)) { fcparam *fcp = FCPARAM(isp, bus); cpi->hba_misc = PIM_NOBUSRESET | PIM_UNMAPPED; /* * Because our loop ID can shift from time to time, * make our initiator ID out of range of our bus. */ cpi->initiator_id = cpi->max_target + 1; /* * Set base transfer capabilities for Fibre Channel, for this HBA. */ if (IS_25XX(isp)) { cpi->base_transfer_speed = 8000000; } else if (IS_24XX(isp)) { cpi->base_transfer_speed = 4000000; } else if (IS_23XX(isp)) { cpi->base_transfer_speed = 2000000; } else { cpi->base_transfer_speed = 1000000; } cpi->hba_inquiry = PI_TAG_ABLE; cpi->transport = XPORT_FC; cpi->transport_version = 0; cpi->xport_specific.fc.wwnn = fcp->isp_wwnn; cpi->xport_specific.fc.wwpn = fcp->isp_wwpn; cpi->xport_specific.fc.port = fcp->isp_portid; cpi->xport_specific.fc.bitrate = fcp->isp_gbspeed * 1000; } else { sdparam *sdp = SDPARAM(isp, bus); cpi->hba_inquiry = PI_SDTR_ABLE|PI_TAG_ABLE|PI_WIDE_16; cpi->hba_misc = PIM_UNMAPPED; cpi->initiator_id = sdp->isp_initiator_id; cpi->base_transfer_speed = 3300; cpi->transport = XPORT_SPI; cpi->transport_version = 2; } cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strncpy(cpi->hba_vid, "Qlogic", HBA_IDLEN); strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } default: ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; } } #define ISPDDB (CAM_DEBUG_INFO|CAM_DEBUG_TRACE|CAM_DEBUG_CDB) void isp_done(XS_T *sccb) { ispsoftc_t *isp = XS_ISP(sccb); uint32_t status; if (XS_NOERR(sccb)) XS_SETERR(sccb, CAM_REQ_CMP); if ((sccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP && (sccb->scsi_status != SCSI_STATUS_OK)) { sccb->ccb_h.status &= ~CAM_STATUS_MASK; if ((sccb->scsi_status == SCSI_STATUS_CHECK_COND) && (sccb->ccb_h.status & CAM_AUTOSNS_VALID) == 0) { sccb->ccb_h.status |= CAM_AUTOSENSE_FAIL; } else { sccb->ccb_h.status |= CAM_SCSI_STATUS_ERROR; } } sccb->ccb_h.status &= ~CAM_SIM_QUEUED; status = sccb->ccb_h.status & CAM_STATUS_MASK; if (status != CAM_REQ_CMP) { if (status != CAM_SEL_TIMEOUT) isp_prt(isp, ISP_LOGDEBUG0, "target %d lun %d CAM status 0x%x SCSI status 0x%x", XS_TGT(sccb), XS_LUN(sccb), sccb->ccb_h.status, sccb->scsi_status); if ((sccb->ccb_h.status & CAM_DEV_QFRZN) == 0) { sccb->ccb_h.status |= CAM_DEV_QFRZN; xpt_freeze_devq(sccb->ccb_h.path, 1); } } if ((CAM_DEBUGGED(sccb->ccb_h.path, ISPDDB)) && (sccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { xpt_print(sccb->ccb_h.path, "cam completion status 0x%x\n", sccb->ccb_h.status); } if (callout_active(&PISP_PCMD(sccb)->wdog)) callout_stop(&PISP_PCMD(sccb)->wdog); isp_free_pcmd(isp, (union ccb *) sccb); xpt_done((union ccb *) sccb); } void isp_async(ispsoftc_t *isp, ispasync_t cmd, ...) { int bus; static const char prom0[] = "Chan %d PortID 0x%06x handle 0x%x %s %s WWPN 0x%08x%08x"; static const char prom2[] = "Chan %d PortID 0x%06x handle 0x%x %s %s tgt %u WWPN 0x%08x%08x"; char buf[64]; char *msg = NULL; target_id_t tgt; fcportdb_t *lp; struct isp_fc *fc; struct cam_path *tmppath; va_list ap; switch (cmd) { case ISPASYNC_NEW_TGT_PARAMS: { struct ccb_trans_settings_scsi *scsi; struct ccb_trans_settings_spi *spi; int flags, tgt; sdparam *sdp; struct ccb_trans_settings cts; memset(&cts, 0, sizeof (struct ccb_trans_settings)); va_start(ap, cmd); bus = va_arg(ap, int); tgt = va_arg(ap, int); va_end(ap); sdp = SDPARAM(isp, bus); if (xpt_create_path(&tmppath, NULL, cam_sim_path(ISP_SPI_PC(isp, bus)->sim), tgt, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { isp_prt(isp, ISP_LOGWARN, "isp_async cannot make temp path for %d.%d", tgt, bus); break; } flags = sdp->isp_devparam[tgt].actv_flags; cts.type = CTS_TYPE_CURRENT_SETTINGS; cts.protocol = PROTO_SCSI; cts.transport = XPORT_SPI; scsi = &cts.proto_specific.scsi; spi = &cts.xport_specific.spi; if (flags & DPARM_TQING) { scsi->valid |= CTS_SCSI_VALID_TQ; scsi->flags |= CTS_SCSI_FLAGS_TAG_ENB; } if (flags & DPARM_DISC) { spi->valid |= CTS_SPI_VALID_DISC; spi->flags |= CTS_SPI_FLAGS_DISC_ENB; } spi->flags |= CTS_SPI_VALID_BUS_WIDTH; if (flags & DPARM_WIDE) { spi->bus_width = MSG_EXT_WDTR_BUS_16_BIT; } else { spi->bus_width = MSG_EXT_WDTR_BUS_8_BIT; } if (flags & DPARM_SYNC) { spi->valid |= CTS_SPI_VALID_SYNC_RATE; spi->valid |= CTS_SPI_VALID_SYNC_OFFSET; spi->sync_period = sdp->isp_devparam[tgt].actv_period; spi->sync_offset = sdp->isp_devparam[tgt].actv_offset; } isp_prt(isp, ISP_LOGDEBUG2, "NEW_TGT_PARAMS bus %d tgt %d period %x offset %x flags %x", bus, tgt, sdp->isp_devparam[tgt].actv_period, sdp->isp_devparam[tgt].actv_offset, flags); xpt_setup_ccb(&cts.ccb_h, tmppath, 1); xpt_async(AC_TRANSFER_NEG, tmppath, &cts); xpt_free_path(tmppath); break; } case ISPASYNC_BUS_RESET: { va_start(ap, cmd); bus = va_arg(ap, int); va_end(ap); isp_prt(isp, ISP_LOGINFO, "SCSI bus reset on bus %d detected", bus); if (IS_FC(isp)) { xpt_async(AC_BUS_RESET, ISP_FC_PC(isp, bus)->path, NULL); } else { xpt_async(AC_BUS_RESET, ISP_SPI_PC(isp, bus)->path, NULL); } break; } case ISPASYNC_LIP: if (msg == NULL) { msg = "LIP Received"; } /* FALLTHROUGH */ case ISPASYNC_LOOP_RESET: if (msg == NULL) { msg = "LOOP Reset"; } /* FALLTHROUGH */ case ISPASYNC_LOOP_DOWN: { if (msg == NULL) { msg = "LOOP Down"; } va_start(ap, cmd); bus = va_arg(ap, int); va_end(ap); FCPARAM(isp, bus)->link_active = 0; fc = ISP_FC_PC(isp, bus); if (cmd == ISPASYNC_LOOP_DOWN && fc->ready) { /* * We don't do any simq freezing if we are only in target mode */ if (FCPARAM(isp, bus)->role & ISP_ROLE_INITIATOR) { if (fc->path) { isp_freeze_loopdown(isp, bus, msg); } if (!callout_active(&fc->ldt)) { callout_reset(&fc->ldt, fc->loop_down_limit * hz, isp_ldt, fc); isp_prt(isp, ISP_LOG_SANCFG|ISP_LOGDEBUG0, "Starting Loop Down Timer @ %lu", (unsigned long) time_uptime); } } } isp_prt(isp, ISP_LOGINFO, "Chan %d: %s", bus, msg); break; } case ISPASYNC_LOOP_UP: va_start(ap, cmd); bus = va_arg(ap, int); va_end(ap); fc = ISP_FC_PC(isp, bus); /* * Now we just note that Loop has come up. We don't * actually do anything because we're waiting for a * Change Notify before activating the FC cleanup * thread to look at the state of the loop again. */ FCPARAM(isp, bus)->link_active = 1; fc->loop_dead = 0; fc->loop_down_time = 0; isp_prt(isp, ISP_LOGINFO, "Chan %d Loop UP", bus); break; case ISPASYNC_DEV_ARRIVED: va_start(ap, cmd); bus = va_arg(ap, int); lp = va_arg(ap, fcportdb_t *); va_end(ap); fc = ISP_FC_PC(isp, bus); lp->announced = 0; lp->gone_timer = 0; if ((FCPARAM(isp, bus)->role & ISP_ROLE_INITIATOR) && (lp->prli_word3 & PRLI_WD3_TARGET_FUNCTION)) { int dbidx = lp - FCPARAM(isp, bus)->portdb; int i; for (i = 0; i < MAX_FC_TARG; i++) { if (i >= FL_ID && i <= SNS_ID) { continue; } if (FCPARAM(isp, bus)->isp_dev_map[i] == 0) { break; } } if (i < MAX_FC_TARG) { FCPARAM(isp, bus)->isp_dev_map[i] = dbidx + 1; lp->dev_map_idx = i + 1; } else { isp_prt(isp, ISP_LOGWARN, "out of target ids"); isp_dump_portdb(isp, bus); } } isp_gen_role_str(buf, sizeof (buf), lp->prli_word3); if (lp->dev_map_idx) { tgt = lp->dev_map_idx - 1; isp_prt(isp, ISP_LOGCONFIG, prom2, bus, lp->portid, lp->handle, buf, "arrived at", tgt, (uint32_t) (lp->port_wwn >> 32), (uint32_t) lp->port_wwn); isp_make_here(isp, bus, tgt); } else { isp_prt(isp, ISP_LOGCONFIG, prom0, bus, lp->portid, lp->handle, buf, "arrived", (uint32_t) (lp->port_wwn >> 32), (uint32_t) lp->port_wwn); } break; case ISPASYNC_DEV_CHANGED: va_start(ap, cmd); bus = va_arg(ap, int); lp = va_arg(ap, fcportdb_t *); va_end(ap); fc = ISP_FC_PC(isp, bus); lp->announced = 0; lp->gone_timer = 0; if (isp_change_is_bad) { lp->state = FC_PORTDB_STATE_NIL; if (lp->dev_map_idx) { tgt = lp->dev_map_idx - 1; FCPARAM(isp, bus)->isp_dev_map[tgt] = 0; lp->dev_map_idx = 0; isp_prt(isp, ISP_LOGCONFIG, prom3, bus, lp->portid, tgt, "change is bad"); isp_make_gone(isp, bus, tgt); } else { isp_gen_role_str(buf, sizeof (buf), lp->prli_word3); isp_prt(isp, ISP_LOGCONFIG, prom0, bus, lp->portid, lp->handle, buf, "changed and departed", (uint32_t) (lp->port_wwn >> 32), (uint32_t) lp->port_wwn); } } else { lp->portid = lp->new_portid; lp->prli_word3 = lp->new_prli_word3; isp_gen_role_str(buf, sizeof (buf), lp->prli_word3); if (lp->dev_map_idx) { int t = lp->dev_map_idx - 1; FCPARAM(isp, bus)->isp_dev_map[t] = (lp - FCPARAM(isp, bus)->portdb) + 1; tgt = lp->dev_map_idx - 1; isp_prt(isp, ISP_LOGCONFIG, prom2, bus, lp->portid, lp->handle, buf, "changed at", tgt, (uint32_t) (lp->port_wwn >> 32), (uint32_t) lp->port_wwn); } else { isp_prt(isp, ISP_LOGCONFIG, prom0, bus, lp->portid, lp->handle, buf, "changed", (uint32_t) (lp->port_wwn >> 32), (uint32_t) lp->port_wwn); } } break; case ISPASYNC_DEV_STAYED: va_start(ap, cmd); bus = va_arg(ap, int); lp = va_arg(ap, fcportdb_t *); va_end(ap); isp_gen_role_str(buf, sizeof (buf), lp->prli_word3); if (lp->dev_map_idx) { tgt = lp->dev_map_idx - 1; isp_prt(isp, ISP_LOGCONFIG, prom2, bus, lp->portid, lp->handle, buf, "stayed at", tgt, (uint32_t) (lp->port_wwn >> 32), (uint32_t) lp->port_wwn); } else { isp_prt(isp, ISP_LOGCONFIG, prom0, bus, lp->portid, lp->handle, buf, "stayed", (uint32_t) (lp->port_wwn >> 32), (uint32_t) lp->port_wwn); } break; case ISPASYNC_DEV_GONE: va_start(ap, cmd); bus = va_arg(ap, int); lp = va_arg(ap, fcportdb_t *); va_end(ap); fc = ISP_FC_PC(isp, bus); /* * If this has a virtual target and we haven't marked it * that we're going to have isp_gdt tell the OS it's gone, * set the isp_gdt timer running on it. * * If it isn't marked that isp_gdt is going to get rid of it, * announce that it's gone. * */ isp_gen_role_str(buf, sizeof (buf), lp->prli_word3); if (lp->dev_map_idx && lp->announced == 0) { lp->announced = 1; lp->state = FC_PORTDB_STATE_ZOMBIE; lp->gone_timer = ISP_FC_PC(isp, bus)->gone_device_time; if (fc->ready && !callout_active(&fc->gdt)) { isp_prt(isp, ISP_LOG_SANCFG|ISP_LOGDEBUG0, "Chan %d Starting Gone Device Timer with %u seconds time now %lu", bus, lp->gone_timer, (unsigned long)time_uptime); callout_reset(&fc->gdt, hz, isp_gdt, fc); } tgt = lp->dev_map_idx - 1; isp_prt(isp, ISP_LOGCONFIG, prom2, bus, lp->portid, lp->handle, buf, "gone zombie at", tgt, (uint32_t) (lp->port_wwn >> 32), (uint32_t) lp->port_wwn); } else if (lp->announced == 0) { isp_prt(isp, ISP_LOGCONFIG, prom0, bus, lp->portid, lp->handle, buf, "departed", (uint32_t) (lp->port_wwn >> 32), (uint32_t) lp->port_wwn); } break; case ISPASYNC_CHANGE_NOTIFY: { char *msg; int evt, nphdl, nlstate, reason; va_start(ap, cmd); bus = va_arg(ap, int); evt = va_arg(ap, int); if (IS_24XX(isp) && evt == ISPASYNC_CHANGE_PDB) { nphdl = va_arg(ap, int); nlstate = va_arg(ap, int); reason = va_arg(ap, int); } else { nphdl = NIL_HANDLE; nlstate = reason = 0; } va_end(ap); fc = ISP_FC_PC(isp, bus); if (evt == ISPASYNC_CHANGE_PDB) { msg = "Chan %d Port Database Changed"; } else if (evt == ISPASYNC_CHANGE_SNS) { msg = "Chan %d Name Server Database Changed"; } else { msg = "Chan %d Other Change Notify"; } /* * If the loop down timer is running, cancel it. */ if (fc->ready && callout_active(&fc->ldt)) { isp_prt(isp, ISP_LOG_SANCFG|ISP_LOGDEBUG0, "Stopping Loop Down Timer @ %lu", (unsigned long) time_uptime); callout_stop(&fc->ldt); } isp_prt(isp, ISP_LOGINFO, msg, bus); if (FCPARAM(isp, bus)->role & ISP_ROLE_INITIATOR) { isp_freeze_loopdown(isp, bus, msg); } wakeup(fc); break; } #ifdef ISP_TARGET_MODE case ISPASYNC_TARGET_NOTIFY: { isp_notify_t *notify; va_start(ap, cmd); notify = va_arg(ap, isp_notify_t *); va_end(ap); switch (notify->nt_ncode) { case NT_ABORT_TASK: case NT_ABORT_TASK_SET: case NT_CLEAR_ACA: case NT_CLEAR_TASK_SET: case NT_LUN_RESET: case NT_TARGET_RESET: /* * These are task management functions. */ isp_handle_platform_target_tmf(isp, notify); break; case NT_BUS_RESET: case NT_LIP_RESET: case NT_LINK_UP: case NT_LINK_DOWN: /* * No action need be taken here. */ break; case NT_HBA_RESET: isp_del_all_wwn_entries(isp, ISP_NOCHAN); break; case NT_GLOBAL_LOGOUT: case NT_LOGOUT: /* * This is device arrival/departure notification */ isp_handle_platform_target_notify_ack(isp, notify); break; case NT_ARRIVED: { struct ac_contract ac; struct ac_device_changed *fc; ac.contract_number = AC_CONTRACT_DEV_CHG; fc = (struct ac_device_changed *) ac.contract_data; fc->wwpn = notify->nt_wwn; fc->port = notify->nt_sid; fc->target = notify->nt_nphdl; fc->arrived = 1; xpt_async(AC_CONTRACT, ISP_FC_PC(isp, notify->nt_channel)->path, &ac); break; } case NT_DEPARTED: { struct ac_contract ac; struct ac_device_changed *fc; ac.contract_number = AC_CONTRACT_DEV_CHG; fc = (struct ac_device_changed *) ac.contract_data; fc->wwpn = notify->nt_wwn; fc->port = notify->nt_sid; fc->target = notify->nt_nphdl; fc->arrived = 0; xpt_async(AC_CONTRACT, ISP_FC_PC(isp, notify->nt_channel)->path, &ac); break; } default: isp_prt(isp, ISP_LOGALL, "target notify code 0x%x", notify->nt_ncode); isp_handle_platform_target_notify_ack(isp, notify); break; } break; } case ISPASYNC_TARGET_NOTIFY_ACK: { void *inot; va_start(ap, cmd); inot = va_arg(ap, void *); va_end(ap); if (isp_notify_ack(isp, inot)) { isp_tna_t *tp = malloc(sizeof (*tp), M_DEVBUF, M_NOWAIT); if (tp) { tp->isp = isp; if (inot) { memcpy(tp->data, inot, sizeof (tp->data)); tp->not = tp->data; } else { tp->not = NULL; } callout_init_mtx(&tp->timer, &isp->isp_lock, 0); callout_reset(&tp->timer, 5, isp_refire_notify_ack, tp); } else { isp_prt(isp, ISP_LOGERR, "you lose- cannot allocate a notify refire"); } } break; } case ISPASYNC_TARGET_ACTION: { isphdr_t *hp; va_start(ap, cmd); hp = va_arg(ap, isphdr_t *); va_end(ap); switch (hp->rqs_entry_type) { default: isp_prt(isp, ISP_LOGWARN, "%s: unhandled target action 0x%x", __func__, hp->rqs_entry_type); break; case RQSTYPE_NOTIFY: if (IS_SCSI(isp)) { isp_handle_platform_notify_scsi(isp, (in_entry_t *) hp); } else if (IS_24XX(isp)) { isp_handle_platform_notify_24xx(isp, (in_fcentry_24xx_t *) hp); } else { isp_handle_platform_notify_fc(isp, (in_fcentry_t *) hp); } break; case RQSTYPE_ATIO: if (IS_24XX(isp)) { isp_handle_platform_atio7(isp, (at7_entry_t *) hp); } else { isp_handle_platform_atio(isp, (at_entry_t *) hp); } break; case RQSTYPE_ATIO2: isp_handle_platform_atio2(isp, (at2_entry_t *) hp); break; case RQSTYPE_CTIO7: case RQSTYPE_CTIO3: case RQSTYPE_CTIO2: case RQSTYPE_CTIO: isp_handle_platform_ctio(isp, hp); break; case RQSTYPE_ABTS_RCVD: { abts_t *abts = (abts_t *)hp; isp_notify_t notify, *nt = ¬ify; tstate_t *tptr; fcportdb_t *lp; uint16_t chan; uint32_t sid, did; did = (abts->abts_did_hi << 16) | abts->abts_did_lo; sid = (abts->abts_sid_hi << 16) | abts->abts_sid_lo; ISP_MEMZERO(nt, sizeof (isp_notify_t)); nt->nt_hba = isp; nt->nt_did = did; nt->nt_nphdl = abts->abts_nphdl; nt->nt_sid = sid; isp_find_chan_by_did(isp, did, &chan); if (chan == ISP_NOCHAN) { nt->nt_tgt = TGT_ANY; } else { nt->nt_tgt = FCPARAM(isp, chan)->isp_wwpn; if (isp_find_pdb_by_loopid(isp, chan, abts->abts_nphdl, &lp)) { nt->nt_wwn = lp->port_wwn; } else { nt->nt_wwn = INI_ANY; } } /* * Try hard to find the lun for this command. */ tptr = get_lun_statep_from_tag(isp, chan, abts->abts_rxid_task); if (tptr) { nt->nt_lun = xpt_path_lun_id(tptr->owner); rls_lun_statep(isp, tptr); } else { nt->nt_lun = LUN_ANY; } nt->nt_need_ack = 1; nt->nt_tagval = abts->abts_rxid_task; nt->nt_tagval |= (((uint64_t) abts->abts_rxid_abts) << 32); if (abts->abts_rxid_task == ISP24XX_NO_TASK) { isp_prt(isp, ISP_LOGTINFO, "[0x%x] ABTS from N-Port handle 0x%x Port 0x%06x has no task id (rx_id 0x%04x ox_id 0x%04x)", abts->abts_rxid_abts, abts->abts_nphdl, sid, abts->abts_rx_id, abts->abts_ox_id); } else { isp_prt(isp, ISP_LOGTINFO, "[0x%x] ABTS from N-Port handle 0x%x Port 0x%06x for task 0x%x (rx_id 0x%04x ox_id 0x%04x)", abts->abts_rxid_abts, abts->abts_nphdl, sid, abts->abts_rxid_task, abts->abts_rx_id, abts->abts_ox_id); } nt->nt_channel = chan; nt->nt_ncode = NT_ABORT_TASK; nt->nt_lreserved = hp; isp_handle_platform_target_tmf(isp, nt); break; } case RQSTYPE_ENABLE_LUN: case RQSTYPE_MODIFY_LUN: isp_ledone(isp, (lun_entry_t *) hp); break; } break; } #endif case ISPASYNC_FW_CRASH: { uint16_t mbox1, mbox6; mbox1 = ISP_READ(isp, OUTMAILBOX1); if (IS_DUALBUS(isp)) { mbox6 = ISP_READ(isp, OUTMAILBOX6); } else { mbox6 = 0; } isp_prt(isp, ISP_LOGERR, "Internal Firmware Error on bus %d @ RISC Address 0x%x", mbox6, mbox1); mbox1 = isp->isp_osinfo.mbox_sleep_ok; isp->isp_osinfo.mbox_sleep_ok = 0; isp_reinit(isp, 1); isp->isp_osinfo.mbox_sleep_ok = mbox1; isp_async(isp, ISPASYNC_FW_RESTARTED, NULL); break; } default: isp_prt(isp, ISP_LOGERR, "unknown isp_async event %d", cmd); break; } } /* * Locks are held before coming here. */ void isp_uninit(ispsoftc_t *isp) { if (IS_24XX(isp)) { ISP_WRITE(isp, BIU2400_HCCR, HCCR_2400_CMD_RESET); } else { ISP_WRITE(isp, HCCR, HCCR_CMD_RESET); } ISP_DISABLE_INTS(isp); } /* * When we want to get the 'default' WWNs (when lacking NVRAM), we pick them * up from our platform default (defww{p|n}n) and morph them based upon * channel. * * When we want to get the 'active' WWNs, we get NVRAM WWNs and then morph them * based upon channel. */ uint64_t isp_default_wwn(ispsoftc_t * isp, int chan, int isactive, int iswwnn) { uint64_t seed; struct isp_fc *fc = ISP_FC_PC(isp, chan); /* * If we're asking for a active WWN, the default overrides get * returned, otherwise the NVRAM value is picked. * * If we're asking for a default WWN, we just pick the default override. */ if (isactive) { seed = iswwnn ? fc->def_wwnn : fc->def_wwpn; if (seed) { return (seed); } seed = iswwnn ? FCPARAM(isp, chan)->isp_wwnn_nvram : FCPARAM(isp, chan)->isp_wwpn_nvram; if (seed) { return (seed); } return (0x400000007F000009ull); } else { seed = iswwnn ? fc->def_wwnn : fc->def_wwpn; } /* * For channel zero just return what we have. For either ACTIVE or * DEFAULT cases, we depend on default override of NVRAM values for * channel zero. */ if (chan == 0) { return (seed); } /* * For other channels, we are doing one of three things: * * 1. If what we have now is non-zero, return it. Otherwise we morph * values from channel 0. 2. If we're here for a WWPN we synthesize * it if Channel 0's wwpn has a type 2 NAA. 3. If we're here for a * WWNN we synthesize it if Channel 0's wwnn has a type 2 NAA. */ if (seed) { return (seed); } if (isactive) { seed = iswwnn ? FCPARAM(isp, 0)->isp_wwnn_nvram : FCPARAM(isp, 0)->isp_wwpn_nvram; } else { seed = iswwnn ? ISP_FC_PC(isp, 0)->def_wwnn : ISP_FC_PC(isp, 0)->def_wwpn; } if (((seed >> 60) & 0xf) == 2) { /* * The type 2 NAA fields for QLogic cards appear be laid out * thusly: * * bits 63..60 NAA == 2 bits 59..57 unused/zero bit 56 * port (1) or node (0) WWN distinguishor bit 48 * physical port on dual-port chips (23XX/24XX) * * This is somewhat nutty, particularly since bit 48 is * irrelevant as they assign separate serial numbers to * different physical ports anyway. * * We'll stick our channel number plus one first into bits * 57..59 and thence into bits 52..55 which allows for 8 bits * of channel which is comfortably more than our maximum * (126) now. */ seed &= ~0x0FF0000000000000ULL; if (iswwnn == 0) { seed |= ((uint64_t) (chan + 1) & 0xf) << 56; seed |= ((uint64_t) ((chan + 1) >> 4) & 0xf) << 52; } } else { seed = 0; } return (seed); } void isp_prt(ispsoftc_t *isp, int level, const char *fmt, ...) { int loc; char lbuf[200]; va_list ap; if (level != ISP_LOGALL && (level & isp->isp_dblev) == 0) { return; } snprintf(lbuf, sizeof (lbuf), "%s: ", device_get_nameunit(isp->isp_dev)); loc = strlen(lbuf); va_start(ap, fmt); vsnprintf(&lbuf[loc], sizeof (lbuf) - loc - 1, fmt, ap); va_end(ap); printf("%s\n", lbuf); } void isp_xs_prt(ispsoftc_t *isp, XS_T *xs, int level, const char *fmt, ...) { va_list ap; if (level != ISP_LOGALL && (level & isp->isp_dblev) == 0) { return; } xpt_print_path(xs->ccb_h.path); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf("\n"); } uint64_t isp_nanotime_sub(struct timespec *b, struct timespec *a) { uint64_t elapsed; struct timespec x = *b; timespecsub(&x, a); elapsed = GET_NANOSEC(&x); if (elapsed == 0) elapsed++; return (elapsed); } int isp_mbox_acquire(ispsoftc_t *isp) { if (isp->isp_osinfo.mboxbsy) { return (1); } else { isp->isp_osinfo.mboxcmd_done = 0; isp->isp_osinfo.mboxbsy = 1; return (0); } } void isp_mbox_wait_complete(ispsoftc_t *isp, mbreg_t *mbp) { unsigned int usecs = mbp->timeout; unsigned int max, olim, ilim; if (usecs == 0) { usecs = MBCMD_DEFAULT_TIMEOUT; } max = isp->isp_mbxwrk0 + 1; if (isp->isp_osinfo.mbox_sleep_ok) { unsigned int ms = (usecs + 999) / 1000; isp->isp_osinfo.mbox_sleep_ok = 0; isp->isp_osinfo.mbox_sleeping = 1; for (olim = 0; olim < max; olim++) { msleep(&isp->isp_mbxworkp, &isp->isp_osinfo.lock, PRIBIO, "ispmbx_sleep", isp_mstohz(ms)); if (isp->isp_osinfo.mboxcmd_done) { break; } } isp->isp_osinfo.mbox_sleep_ok = 1; isp->isp_osinfo.mbox_sleeping = 0; } else { for (olim = 0; olim < max; olim++) { for (ilim = 0; ilim < usecs; ilim += 100) { uint32_t isr; uint16_t sema, mbox; if (isp->isp_osinfo.mboxcmd_done) { break; } if (ISP_READ_ISR(isp, &isr, &sema, &mbox)) { isp_intr(isp, isr, sema, mbox); if (isp->isp_osinfo.mboxcmd_done) { break; } } ISP_DELAY(100); } if (isp->isp_osinfo.mboxcmd_done) { break; } } } if (isp->isp_osinfo.mboxcmd_done == 0) { isp_prt(isp, ISP_LOGWARN, "%s Mailbox Command (0x%x) Timeout (%uus) (started @ %s:%d)", isp->isp_osinfo.mbox_sleep_ok? "Interrupting" : "Polled", isp->isp_lastmbxcmd, usecs, mbp->func, mbp->lineno); mbp->param[0] = MBOX_TIMEOUT; isp->isp_osinfo.mboxcmd_done = 1; } } void isp_mbox_notify_done(ispsoftc_t *isp) { if (isp->isp_osinfo.mbox_sleeping) { wakeup(&isp->isp_mbxworkp); } isp->isp_osinfo.mboxcmd_done = 1; } void isp_mbox_release(ispsoftc_t *isp) { isp->isp_osinfo.mboxbsy = 0; } int isp_fc_scratch_acquire(ispsoftc_t *isp, int chan) { int ret = 0; if (isp->isp_osinfo.pc.fc[chan].fcbsy) { ret = -1; } else { isp->isp_osinfo.pc.fc[chan].fcbsy = 1; } return (ret); } int isp_mstohz(int ms) { int hz; struct timeval t; t.tv_sec = ms / 1000; t.tv_usec = (ms % 1000) * 1000; hz = tvtohz(&t); if (hz < 0) { hz = 0x7fffffff; } if (hz == 0) { hz = 1; } return (hz); } void isp_platform_intr(void *arg) { ispsoftc_t *isp = arg; uint32_t isr; uint16_t sema, mbox; ISP_LOCK(isp); isp->isp_intcnt++; if (ISP_READ_ISR(isp, &isr, &sema, &mbox) == 0) { isp->isp_intbogus++; } else { isp_intr(isp, isr, sema, mbox); } ISP_UNLOCK(isp); } void isp_common_dmateardown(ispsoftc_t *isp, struct ccb_scsiio *csio, uint32_t hdl) { if ((csio->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) { bus_dmamap_sync(isp->isp_osinfo.dmat, PISP_PCMD(csio)->dmap, BUS_DMASYNC_POSTREAD); } else { bus_dmamap_sync(isp->isp_osinfo.dmat, PISP_PCMD(csio)->dmap, BUS_DMASYNC_POSTWRITE); } bus_dmamap_unload(isp->isp_osinfo.dmat, PISP_PCMD(csio)->dmap); } int isp_fcp_next_crn(ispsoftc_t *isp, uint8_t *crnp, XS_T *cmd) { uint32_t chan, tgt, lun; struct isp_fc *fc; struct isp_nexus *nxp; int idx; if (isp->isp_type < ISP_HA_FC_2300) return (0); chan = XS_CHANNEL(cmd); tgt = XS_TGT(cmd); lun = XS_LUN(cmd); fc = &isp->isp_osinfo.pc.fc[chan]; idx = NEXUS_HASH(tgt, lun); nxp = fc->nexus_hash[idx]; while (nxp) { if (nxp->tgt == tgt && nxp->lun == lun) break; nxp = nxp->next; } if (nxp == NULL) { nxp = fc->nexus_free_list; if (nxp == NULL) { nxp = malloc(sizeof (struct isp_nexus), M_DEVBUF, M_ZERO|M_NOWAIT); if (nxp == NULL) { return (-1); } } else { fc->nexus_free_list = nxp->next; } nxp->tgt = tgt; nxp->lun = lun; nxp->next = fc->nexus_hash[idx]; fc->nexus_hash[idx] = nxp; } if (nxp) { if (nxp->crnseed == 0) nxp->crnseed = 1; if (cmd) PISP_PCMD(cmd)->crn = nxp->crnseed; *crnp = nxp->crnseed++; return (0); } return (-1); } /* * We enter with the lock held */ void isp_timer(void *arg) { ispsoftc_t *isp = arg; #ifdef ISP_TARGET_MODE isp_tmcmd_restart(isp); #endif callout_reset(&isp->isp_osinfo.tmo, isp_timer_count, isp_timer, isp); } isp_ecmd_t * isp_get_ecmd(ispsoftc_t *isp) { isp_ecmd_t *ecmd = isp->isp_osinfo.ecmd_free; if (ecmd) { isp->isp_osinfo.ecmd_free = ecmd->next; } return (ecmd); } void isp_put_ecmd(ispsoftc_t *isp, isp_ecmd_t *ecmd) { ecmd->next = isp->isp_osinfo.ecmd_free; isp->isp_osinfo.ecmd_free = ecmd; } Index: projects/sendfile/sys/dev/isp/isp_freebsd.h =================================================================== --- projects/sendfile/sys/dev/isp/isp_freebsd.h (revision 274716) +++ projects/sendfile/sys/dev/isp/isp_freebsd.h (revision 274717) @@ -1,786 +1,786 @@ /* $FreeBSD$ */ /*- * Qlogic ISP SCSI Host Adapter FreeBSD Wrapper Definitions * * Copyright (c) 1997-2008 by Matthew Jacob * All rights reserved. * * 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 immediately at the beginning of the file, without modification, * this list of conditions, and the following disclaimer. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #ifndef _ISP_FREEBSD_H #define _ISP_FREEBSD_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_ddb.h" #include "opt_isp.h" #define ISP_PLATFORM_VERSION_MAJOR 7 #define ISP_PLATFORM_VERSION_MINOR 10 /* * Efficiency- get rid of SBus code && tests unless we need them. */ #ifdef __sparc64__ #define ISP_SBUS_SUPPORTED 1 #else #define ISP_SBUS_SUPPORTED 0 #endif #define ISP_IFLAGS INTR_TYPE_CAM | INTR_ENTROPY | INTR_MPSAFE #define N_XCMDS 64 #define XCMD_SIZE 512 struct ispsoftc; typedef union isp_ecmd { union isp_ecmd * next; uint8_t data[XCMD_SIZE]; } isp_ecmd_t; isp_ecmd_t * isp_get_ecmd(struct ispsoftc *); void isp_put_ecmd(struct ispsoftc *, isp_ecmd_t *); #ifdef ISP_TARGET_MODE /* Not quite right, but there was no bump for this change */ #if __FreeBSD_version < 225469 #define SDFIXED(x) (&x) #else #define SDFIXED(x) ((struct scsi_sense_data_fixed *)(&x)) #endif #define ISP_TARGET_FUNCTIONS 1 #define ATPDPSIZE 4096 #define ATPDPHASHSIZE 16 #define ATPDPHASH(x) ((((x) >> 24) ^ ((x) >> 16) ^ ((x) >> 8) ^ (x)) & \ ((ATPDPHASHSIZE) - 1)) #include typedef struct atio_private_data { LIST_ENTRY(atio_private_data) next; uint32_t orig_datalen; uint32_t bytes_xfered; uint32_t bytes_in_transit; uint32_t tag; /* typically f/w RX_ID */ uint32_t lun; uint32_t nphdl; uint32_t sid; uint32_t portid; uint16_t rxid; /* wire rxid */ uint16_t oxid; /* wire oxid */ uint16_t word3; /* PRLI word3 params */ uint16_t ctcnt; /* number of CTIOs currently active */ uint8_t seqno; /* CTIO sequence number */ uint32_t srr_notify_rcvd : 1, cdb0 : 8, sendst : 1, dead : 1, tattr : 3, state : 3; void * ests; /* * The current SRR notify copy */ uint8_t srr[64]; /* sb QENTRY_LEN, but order of definitions is wrong */ void * srr_ccb; uint32_t nsrr; } atio_private_data_t; #define ATPD_STATE_FREE 0 #define ATPD_STATE_ATIO 1 #define ATPD_STATE_CAM 2 #define ATPD_STATE_CTIO 3 #define ATPD_STATE_LAST_CTIO 4 #define ATPD_STATE_PDON 5 #define ATPD_CCB_OUTSTANDING 16 #define ATPD_SEQ_MASK 0x7f #define ATPD_SEQ_NOTIFY_CAM 0x80 #define ATPD_SET_SEQNO(hdrp, atp) ((isphdr_t *)hdrp)->rqs_seqno &= ~ATPD_SEQ_MASK, ((isphdr_t *)hdrp)->rqs_seqno |= (atp)->seqno #define ATPD_GET_SEQNO(hdrp) (((isphdr_t *)hdrp)->rqs_seqno & ATPD_SEQ_MASK) #define ATPD_GET_NCAM(hdrp) ((((isphdr_t *)hdrp)->rqs_seqno & ATPD_SEQ_NOTIFY_CAM) != 0) typedef union inot_private_data inot_private_data_t; union inot_private_data { inot_private_data_t *next; struct { isp_notify_t nt; /* must be first! */ uint8_t data[64]; /* sb QENTRY_LEN, but order of definitions is wrong */ uint32_t tag_id, seq_id; } rd; }; typedef struct isp_timed_notify_ack { void *isp; void *not; uint8_t data[64]; /* sb QENTRY_LEN, but order of definitions is wrong */ struct callout timer; } isp_tna_t; TAILQ_HEAD(isp_ccbq, ccb_hdr); typedef struct tstate { SLIST_ENTRY(tstate) next; struct cam_path *owner; struct isp_ccbq waitq; /* waiting CCBs */ struct ccb_hdr_slist atios; struct ccb_hdr_slist inots; uint32_t hold; uint32_t enabled : 1, atio_count : 15, inot_count : 15; inot_private_data_t * restart_queue; inot_private_data_t * ntfree; inot_private_data_t ntpool[ATPDPSIZE]; LIST_HEAD(, atio_private_data) atfree; LIST_HEAD(, atio_private_data) atused[ATPDPHASHSIZE]; atio_private_data_t atpool[ATPDPSIZE]; } tstate_t; #define LUN_HASH_SIZE 32 #define LUN_HASH_FUNC(lun) ((lun) & (LUN_HASH_SIZE - 1)) #endif /* * Per command info. */ struct isp_pcmd { struct isp_pcmd * next; bus_dmamap_t dmap; /* dma map for this command */ struct ispsoftc * isp; /* containing isp */ struct callout wdog; /* watchdog timer */ uint32_t datalen; /* data length for this command (target mode only) */ uint8_t totslen; /* sense length on status response */ uint8_t cumslen; /* sense length on status response */ uint8_t crn; /* command reference number */ }; #define ISP_PCMD(ccb) (ccb)->ccb_h.spriv_ptr1 #define PISP_PCMD(ccb) ((struct isp_pcmd *)ISP_PCMD(ccb)) /* * Per nexus info. */ struct isp_nexus { struct isp_nexus * next; uint32_t crnseed : 8; /* next command reference number */ uint32_t tgt : 16, /* TGT for target */ lun : 16; /* LUN for target */ }; #define NEXUS_HASH_WIDTH 32 #define INITIAL_NEXUS_COUNT MAX_FC_TARG #define NEXUS_HASH(tgt, lun) ((tgt + lun) % NEXUS_HASH_WIDTH) /* * Per channel information */ SLIST_HEAD(tslist, tstate); struct isp_fc { struct cam_sim *sim; struct cam_path *path; struct ispsoftc *isp; struct proc *kproc; bus_dma_tag_t tdmat; bus_dmamap_t tdmap; uint64_t def_wwpn; uint64_t def_wwnn; uint32_t loop_down_time; uint32_t loop_down_limit; uint32_t gone_device_time; /* * Per target/lun info- just to keep a per-ITL nexus crn count */ struct isp_nexus *nexus_hash[NEXUS_HASH_WIDTH]; struct isp_nexus *nexus_free_list; uint32_t #ifdef ISP_TARGET_MODE #ifdef ISP_INTERNAL_TARGET proc_active : 1, #endif tm_luns_enabled : 1, tm_enable_defer : 1, tm_enabled : 1, #endif simqfrozen : 3, default_id : 8, hysteresis : 8, def_role : 2, /* default role */ gdt_running : 1, loop_dead : 1, fcbsy : 1, ready : 1; struct callout ldt; /* loop down timer */ struct callout gdt; /* gone device timer */ struct task ltask; struct task gtask; #ifdef ISP_TARGET_MODE struct tslist lun_hash[LUN_HASH_SIZE]; #ifdef ISP_INTERNAL_TARGET struct proc * target_proc; #endif #if defined(DEBUG) unsigned int inject_lost_data_frame; #endif #endif int num_threads; }; struct isp_spi { struct cam_sim *sim; struct cam_path *path; uint32_t #ifdef ISP_TARGET_MODE #ifdef ISP_INTERNAL_TARGET proc_active : 1, #endif tm_luns_enabled : 1, tm_enable_defer : 1, tm_enabled : 1, #endif simqfrozen : 3, def_role : 2, iid : 4; #ifdef ISP_TARGET_MODE struct tslist lun_hash[LUN_HASH_SIZE]; #ifdef ISP_INTERNAL_TARGET struct proc * target_proc; #endif #endif int num_threads; }; struct isposinfo { /* * Linkage, locking, and identity */ struct mtx lock; device_t dev; struct cdev * cdev; struct intr_config_hook ehook; struct cam_devq * devq; /* * Firmware pointer */ const struct firmware * fw; /* * DMA related sdtuff */ bus_space_tag_t bus_tag; bus_dma_tag_t dmat; bus_space_handle_t bus_handle; bus_dma_tag_t cdmat; bus_dmamap_t cdmap; /* * Command and transaction related related stuff */ struct isp_pcmd * pcmd_pool; struct isp_pcmd * pcmd_free; uint32_t #ifdef ISP_TARGET_MODE tmwanted : 1, tmbusy : 1, #else : 2, #endif sixtyfourbit : 1, /* sixtyfour bit platform */ timer_active : 1, autoconf : 1, ehook_active : 1, disabled : 1, mbox_sleeping : 1, mbox_sleep_ok : 1, mboxcmd_done : 1, mboxbsy : 1; struct callout tmo; /* general timer */ /* * misc- needs to be sorted better XXXXXX */ int framesize; int exec_throttle; int cont_max; #ifdef ISP_TARGET_MODE cam_status * rptr; #endif bus_addr_t ecmd_dma; isp_ecmd_t * ecmd_base; isp_ecmd_t * ecmd_free; /* * Per-type private storage... */ union { struct isp_fc *fc; struct isp_spi *spi; void *ptr; } pc; int is_exiting; }; #define ISP_FC_PC(isp, chan) (&(isp)->isp_osinfo.pc.fc[(chan)]) #define ISP_SPI_PC(isp, chan) (&(isp)->isp_osinfo.pc.spi[(chan)]) #define ISP_GET_PC(isp, chan, tag, rslt) \ if (IS_SCSI(isp)) { \ rslt = ISP_SPI_PC(isp, chan)-> tag; \ } else { \ rslt = ISP_FC_PC(isp, chan)-> tag; \ } #define ISP_GET_PC_ADDR(isp, chan, tag, rp) \ if (IS_SCSI(isp)) { \ rp = &ISP_SPI_PC(isp, chan)-> tag; \ } else { \ rp = &ISP_FC_PC(isp, chan)-> tag; \ } #define ISP_SET_PC(isp, chan, tag, val) \ if (IS_SCSI(isp)) { \ ISP_SPI_PC(isp, chan)-> tag = val; \ } else { \ ISP_FC_PC(isp, chan)-> tag = val; \ } #define FCP_NEXT_CRN isp_fcp_next_crn #define isp_lock isp_osinfo.lock #define isp_bus_tag isp_osinfo.bus_tag #define isp_bus_handle isp_osinfo.bus_handle /* * Locking macros... */ -#define ISP_LOCK(isp) mtx_lock(&isp->isp_osinfo.lock) -#define ISP_UNLOCK(isp) mtx_unlock(&isp->isp_osinfo.lock) -#define ISP_ASSERT_LOCKED(isp) mtx_assert(&isp->isp_osinfo.lock, MA_OWNED) +#define ISP_LOCK(isp) mtx_lock(&(isp)->isp_osinfo.lock) +#define ISP_UNLOCK(isp) mtx_unlock(&(isp)->isp_osinfo.lock) +#define ISP_ASSERT_LOCKED(isp) mtx_assert(&(isp)->isp_osinfo.lock, MA_OWNED) /* * Required Macros/Defines */ #define ISP_FC_SCRLEN 0x1000 #define ISP_MEMZERO(a, b) memset(a, 0, b) #define ISP_MEMCPY memcpy #define ISP_SNPRINTF snprintf #define ISP_DELAY DELAY #define ISP_SLEEP(isp, x) DELAY(x) #define ISP_MIN imin #ifndef DIAGNOSTIC #define ISP_INLINE __inline #else #define ISP_INLINE #endif #define NANOTIME_T struct timespec #define GET_NANOTIME nanotime #define GET_NANOSEC(x) ((x)->tv_sec * 1000000000 + (x)->tv_nsec) #define NANOTIME_SUB isp_nanotime_sub #define MAXISPREQUEST(isp) ((IS_FC(isp) || IS_ULTRA2(isp))? 1024 : 256) #define MEMORYBARRIER(isp, type, offset, size, chan) \ switch (type) { \ case SYNC_SFORDEV: \ { \ struct isp_fc *fc = ISP_FC_PC(isp, chan); \ bus_dmamap_sync(fc->tdmat, fc->tdmap, \ BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); \ break; \ } \ case SYNC_REQUEST: \ bus_dmamap_sync(isp->isp_osinfo.cdmat, \ isp->isp_osinfo.cdmap, \ BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); \ break; \ case SYNC_SFORCPU: \ { \ struct isp_fc *fc = ISP_FC_PC(isp, chan); \ bus_dmamap_sync(fc->tdmat, fc->tdmap, \ BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); \ break; \ } \ case SYNC_RESULT: \ bus_dmamap_sync(isp->isp_osinfo.cdmat, \ isp->isp_osinfo.cdmap, \ BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); \ break; \ case SYNC_REG: \ bus_space_barrier(isp->isp_osinfo.bus_tag, \ isp->isp_osinfo.bus_handle, offset, size, \ BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); \ break; \ default: \ break; \ } #define MEMORYBARRIERW(isp, type, offset, size, chan) \ switch (type) { \ case SYNC_SFORDEV: \ { \ struct isp_fc *fc = ISP_FC_PC(isp, chan); \ bus_dmamap_sync(fc->tdmat, fc->tdmap, \ BUS_DMASYNC_PREWRITE); \ break; \ } \ case SYNC_REQUEST: \ bus_dmamap_sync(isp->isp_osinfo.cdmat, \ isp->isp_osinfo.cdmap, BUS_DMASYNC_PREWRITE); \ break; \ case SYNC_SFORCPU: \ { \ struct isp_fc *fc = ISP_FC_PC(isp, chan); \ bus_dmamap_sync(fc->tdmat, fc->tdmap, \ BUS_DMASYNC_POSTWRITE); \ break; \ } \ case SYNC_RESULT: \ bus_dmamap_sync(isp->isp_osinfo.cdmat, \ isp->isp_osinfo.cdmap, BUS_DMASYNC_POSTWRITE); \ break; \ case SYNC_REG: \ bus_space_barrier(isp->isp_osinfo.bus_tag, \ isp->isp_osinfo.bus_handle, offset, size, \ BUS_SPACE_BARRIER_WRITE); \ break; \ default: \ break; \ } #define MBOX_ACQUIRE isp_mbox_acquire #define MBOX_WAIT_COMPLETE isp_mbox_wait_complete #define MBOX_NOTIFY_COMPLETE isp_mbox_notify_done #define MBOX_RELEASE isp_mbox_release #define FC_SCRATCH_ACQUIRE isp_fc_scratch_acquire #define FC_SCRATCH_RELEASE(isp, chan) isp->isp_osinfo.pc.fc[chan].fcbsy = 0 #ifndef SCSI_GOOD #define SCSI_GOOD SCSI_STATUS_OK #endif #ifndef SCSI_CHECK #define SCSI_CHECK SCSI_STATUS_CHECK_COND #endif #ifndef SCSI_BUSY #define SCSI_BUSY SCSI_STATUS_BUSY #endif #ifndef SCSI_QFULL #define SCSI_QFULL SCSI_STATUS_QUEUE_FULL #endif #define XS_T struct ccb_scsiio #define XS_DMA_ADDR_T bus_addr_t #define XS_GET_DMA64_SEG(a, b, c) \ { \ ispds64_t *d = a; \ bus_dma_segment_t *e = b; \ uint32_t f = c; \ e += f; \ d->ds_base = DMA_LO32(e->ds_addr); \ d->ds_basehi = DMA_HI32(e->ds_addr); \ d->ds_count = e->ds_len; \ } #define XS_GET_DMA_SEG(a, b, c) \ { \ ispds_t *d = a; \ bus_dma_segment_t *e = b; \ uint32_t f = c; \ e += f; \ d->ds_base = DMA_LO32(e->ds_addr); \ d->ds_count = e->ds_len; \ } #define XS_ISP(ccb) cam_sim_softc(xpt_path_sim((ccb)->ccb_h.path)) #define XS_CHANNEL(ccb) cam_sim_bus(xpt_path_sim((ccb)->ccb_h.path)) #define XS_TGT(ccb) (ccb)->ccb_h.target_id #define XS_LUN(ccb) (uint32_t)((ccb)->ccb_h.target_lun) #define XS_CDBP(ccb) \ (((ccb)->ccb_h.flags & CAM_CDB_POINTER)? \ (ccb)->cdb_io.cdb_ptr : (ccb)->cdb_io.cdb_bytes) #define XS_CDBLEN(ccb) (ccb)->cdb_len #define XS_XFRLEN(ccb) (ccb)->dxfer_len #define XS_TIME(ccb) (ccb)->ccb_h.timeout #define XS_GET_RESID(ccb) (ccb)->resid #define XS_SET_RESID(ccb, r) (ccb)->resid = r #define XS_STSP(ccb) (&(ccb)->scsi_status) #define XS_SNSP(ccb) (&(ccb)->sense_data) #define XS_TOT_SNSLEN(ccb) ccb->sense_len #define XS_CUR_SNSLEN(ccb) (ccb->sense_len - ccb->sense_resid) #define XS_SNSKEY(ccb) (scsi_get_sense_key(&(ccb)->sense_data, \ ccb->sense_len - ccb->sense_resid, 1)) #define XS_SNSASC(ccb) (scsi_get_asc(&(ccb)->sense_data, \ ccb->sense_len - ccb->sense_resid, 1)) #define XS_SNSASCQ(ccb) (scsi_get_ascq(&(ccb)->sense_data, \ ccb->sense_len - ccb->sense_resid, 1)) #define XS_TAG_P(ccb) \ (((ccb)->ccb_h.flags & CAM_TAG_ACTION_VALID) && \ (ccb)->tag_action != CAM_TAG_ACTION_NONE) #define XS_TAG_TYPE(ccb) \ ((ccb->tag_action == MSG_SIMPLE_Q_TAG)? REQFLAG_STAG : \ ((ccb->tag_action == MSG_HEAD_OF_Q_TAG)? REQFLAG_HTAG : REQFLAG_OTAG)) #define XS_SETERR(ccb, v) (ccb)->ccb_h.status &= ~CAM_STATUS_MASK, \ (ccb)->ccb_h.status |= v # define HBA_NOERROR CAM_REQ_INPROG # define HBA_BOTCH CAM_UNREC_HBA_ERROR # define HBA_CMDTIMEOUT CAM_CMD_TIMEOUT # define HBA_SELTIMEOUT CAM_SEL_TIMEOUT # define HBA_TGTBSY CAM_SCSI_STATUS_ERROR # define HBA_BUSRESET CAM_SCSI_BUS_RESET # define HBA_ABORTED CAM_REQ_ABORTED # define HBA_DATAOVR CAM_DATA_RUN_ERR # define HBA_ARQFAIL CAM_AUTOSENSE_FAIL #define XS_ERR(ccb) ((ccb)->ccb_h.status & CAM_STATUS_MASK) #define XS_NOERR(ccb) (((ccb)->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_INPROG) #define XS_INITERR(ccb) XS_SETERR(ccb, CAM_REQ_INPROG), ccb->sense_resid = ccb->sense_len #define XS_SAVE_SENSE(xs, sense_ptr, totslen, slen) do { \ uint32_t tlen = slen; \ if (tlen > (xs)->sense_len) \ tlen = (xs)->sense_len; \ PISP_PCMD(xs)->totslen = imin((xs)->sense_len, totslen); \ PISP_PCMD(xs)->cumslen = tlen; \ memcpy(&(xs)->sense_data, sense_ptr, tlen); \ (xs)->sense_resid = (xs)->sense_len - tlen; \ (xs)->ccb_h.status |= CAM_AUTOSNS_VALID; \ } while (0) #define XS_SENSE_APPEND(xs, xsnsp, xsnsl) do { \ uint32_t off = PISP_PCMD(xs)->cumslen; \ uint8_t *ptr = &((uint8_t *)(&(xs)->sense_data))[off]; \ uint32_t amt = imin(xsnsl, PISP_PCMD(xs)->totslen - off); \ if (amt) { \ memcpy(ptr, xsnsp, amt); \ (xs)->sense_resid -= amt; \ PISP_PCMD(xs)->cumslen += amt; \ } \ } while (0) #define XS_SENSE_VALID(xs) (((xs)->ccb_h.status & CAM_AUTOSNS_VALID) != 0) #define DEFAULT_FRAMESIZE(isp) isp->isp_osinfo.framesize #define DEFAULT_EXEC_THROTTLE(isp) isp->isp_osinfo.exec_throttle #define GET_DEFAULT_ROLE(isp, chan) \ (IS_FC(isp)? ISP_FC_PC(isp, chan)->def_role : ISP_SPI_PC(isp, chan)->def_role) #define SET_DEFAULT_ROLE(isp, chan, val) \ if (IS_FC(isp)) { \ ISP_FC_PC(isp, chan)->def_role = val; \ } else { \ ISP_SPI_PC(isp, chan)->def_role = val; \ } #define DEFAULT_IID(isp, chan) isp->isp_osinfo.pc.spi[chan].iid #define DEFAULT_LOOPID(x, chan) isp->isp_osinfo.pc.fc[chan].default_id #define DEFAULT_NODEWWN(isp, chan) isp_default_wwn(isp, chan, 0, 1) #define DEFAULT_PORTWWN(isp, chan) isp_default_wwn(isp, chan, 0, 0) #define ACTIVE_NODEWWN(isp, chan) isp_default_wwn(isp, chan, 1, 1) #define ACTIVE_PORTWWN(isp, chan) isp_default_wwn(isp, chan, 1, 0) #if BYTE_ORDER == BIG_ENDIAN #ifdef ISP_SBUS_SUPPORTED #define ISP_IOXPUT_8(isp, s, d) *(d) = s #define ISP_IOXPUT_16(isp, s, d) \ *(d) = (isp->isp_bustype == ISP_BT_SBUS)? s : bswap16(s) #define ISP_IOXPUT_32(isp, s, d) \ *(d) = (isp->isp_bustype == ISP_BT_SBUS)? s : bswap32(s) #define ISP_IOXGET_8(isp, s, d) d = (*((uint8_t *)s)) #define ISP_IOXGET_16(isp, s, d) \ d = (isp->isp_bustype == ISP_BT_SBUS)? \ *((uint16_t *)s) : bswap16(*((uint16_t *)s)) #define ISP_IOXGET_32(isp, s, d) \ d = (isp->isp_bustype == ISP_BT_SBUS)? \ *((uint32_t *)s) : bswap32(*((uint32_t *)s)) #else /* ISP_SBUS_SUPPORTED */ #define ISP_IOXPUT_8(isp, s, d) *(d) = s #define ISP_IOXPUT_16(isp, s, d) *(d) = bswap16(s) #define ISP_IOXPUT_32(isp, s, d) *(d) = bswap32(s) #define ISP_IOXGET_8(isp, s, d) d = (*((uint8_t *)s)) #define ISP_IOXGET_16(isp, s, d) d = bswap16(*((uint16_t *)s)) #define ISP_IOXGET_32(isp, s, d) d = bswap32(*((uint32_t *)s)) #endif #define ISP_SWIZZLE_NVRAM_WORD(isp, rp) *rp = bswap16(*rp) #define ISP_SWIZZLE_NVRAM_LONG(isp, rp) *rp = bswap32(*rp) #define ISP_IOZGET_8(isp, s, d) d = (*((uint8_t *)s)) #define ISP_IOZGET_16(isp, s, d) d = (*((uint16_t *)s)) #define ISP_IOZGET_32(isp, s, d) d = (*((uint32_t *)s)) #define ISP_IOZPUT_8(isp, s, d) *(d) = s #define ISP_IOZPUT_16(isp, s, d) *(d) = s #define ISP_IOZPUT_32(isp, s, d) *(d) = s #else #define ISP_IOXPUT_8(isp, s, d) *(d) = s #define ISP_IOXPUT_16(isp, s, d) *(d) = s #define ISP_IOXPUT_32(isp, s, d) *(d) = s #define ISP_IOXGET_8(isp, s, d) d = *(s) #define ISP_IOXGET_16(isp, s, d) d = *(s) #define ISP_IOXGET_32(isp, s, d) d = *(s) #define ISP_SWIZZLE_NVRAM_WORD(isp, rp) #define ISP_SWIZZLE_NVRAM_LONG(isp, rp) #define ISP_IOZPUT_8(isp, s, d) *(d) = s #define ISP_IOZPUT_16(isp, s, d) *(d) = bswap16(s) #define ISP_IOZPUT_32(isp, s, d) *(d) = bswap32(s) #define ISP_IOZGET_8(isp, s, d) d = (*((uint8_t *)(s))) #define ISP_IOZGET_16(isp, s, d) d = bswap16(*((uint16_t *)(s))) #define ISP_IOZGET_32(isp, s, d) d = bswap32(*((uint32_t *)(s))) #endif #define ISP_SWAP16(isp, s) bswap16(s) #define ISP_SWAP32(isp, s) bswap32(s) /* * Includes of common header files */ #include #include #include /* * isp_osinfo definiitions && shorthand */ #define SIMQFRZ_RESOURCE 0x1 #define SIMQFRZ_LOOPDOWN 0x2 #define SIMQFRZ_TIMED 0x4 #define isp_dev isp_osinfo.dev /* * prototypes for isp_pci && isp_freebsd to share */ extern int isp_attach(ispsoftc_t *); extern int isp_detach(ispsoftc_t *); extern void isp_uninit(ispsoftc_t *); extern uint64_t isp_default_wwn(ispsoftc_t *, int, int, int); /* * driver global data */ extern int isp_announced; extern int isp_fabric_hysteresis; extern int isp_loop_down_limit; extern int isp_gone_device_time; extern int isp_quickboot_time; extern int isp_autoconfig; /* * Platform private flags */ /* * Platform Library Functions */ void isp_prt(ispsoftc_t *, int level, const char *, ...) __printflike(3, 4); void isp_xs_prt(ispsoftc_t *, XS_T *, int level, const char *, ...) __printflike(4, 5); uint64_t isp_nanotime_sub(struct timespec *, struct timespec *); int isp_mbox_acquire(ispsoftc_t *); void isp_mbox_wait_complete(ispsoftc_t *, mbreg_t *); void isp_mbox_notify_done(ispsoftc_t *); void isp_mbox_release(ispsoftc_t *); int isp_fc_scratch_acquire(ispsoftc_t *, int); int isp_mstohz(int); void isp_platform_intr(void *); void isp_common_dmateardown(ispsoftc_t *, struct ccb_scsiio *, uint32_t); int isp_fcp_next_crn(ispsoftc_t *, uint8_t *, XS_T *); /* * Platform Version specific defines */ #define BUS_DMA_ROOTARG(x) bus_get_dma_tag(x) #define isp_dma_tag_create(a, b, c, d, e, f, g, h, i, j, k, z) \ bus_dma_tag_create(a, b, c, d, e, f, g, h, i, j, k, \ busdma_lock_mutex, &isp->isp_osinfo.lock, z) #define isp_setup_intr bus_setup_intr #define isp_sim_alloc(a, b, c, d, e, f, g, h) \ cam_sim_alloc(a, b, c, d, e, &(d)->isp_osinfo.lock, f, g, h) #define ISP_PATH_PRT(i, l, p, ...) \ if ((l) == ISP_LOGALL || ((l)& (i)->isp_dblev) != 0) { \ xpt_print(p, __VA_ARGS__); \ } /* * Platform specific inline functions */ /* * ISP General Library functions */ #include #endif /* _ISP_FREEBSD_H */ Index: projects/sendfile/sys/dev/pci/pci.c =================================================================== --- projects/sendfile/sys/dev/pci/pci.c (revision 274716) +++ projects/sendfile/sys/dev/pci/pci.c (revision 274717) @@ -1,5113 +1,5115 @@ /*- * Copyright (c) 1997, Stefan Esser * Copyright (c) 2000, Michael Smith * Copyright (c) 2000, BSDi * All rights reserved. * * 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 unmodified, 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_bus.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__i386__) || defined(__amd64__) || defined(__powerpc__) #include #endif #include #include #include #include #include #include #include #include #include "pcib_if.h" #include "pci_if.h" #define PCIR_IS_BIOS(cfg, reg) \ (((cfg)->hdrtype == PCIM_HDRTYPE_NORMAL && reg == PCIR_BIOS) || \ ((cfg)->hdrtype == PCIM_HDRTYPE_BRIDGE && reg == PCIR_BIOS_1)) static int pci_has_quirk(uint32_t devid, int quirk); static pci_addr_t pci_mapbase(uint64_t mapreg); static const char *pci_maptype(uint64_t mapreg); static int pci_mapsize(uint64_t testval); static int pci_maprange(uint64_t mapreg); static pci_addr_t pci_rombase(uint64_t mapreg); static int pci_romsize(uint64_t testval); static void pci_fixancient(pcicfgregs *cfg); static int pci_printf(pcicfgregs *cfg, const char *fmt, ...); static int pci_porten(device_t dev); static int pci_memen(device_t dev); static void pci_assign_interrupt(device_t bus, device_t dev, int force_route); static int pci_add_map(device_t bus, device_t dev, int reg, struct resource_list *rl, int force, int prefetch); static int pci_probe(device_t dev); static int pci_attach(device_t dev); #ifdef PCI_RES_BUS static int pci_detach(device_t dev); #endif static void pci_load_vendor_data(void); static int pci_describe_parse_line(char **ptr, int *vendor, int *device, char **desc); static char *pci_describe_device(device_t dev); static int pci_modevent(module_t mod, int what, void *arg); static void pci_hdrtypedata(device_t pcib, int b, int s, int f, pcicfgregs *cfg); static void pci_read_cap(device_t pcib, pcicfgregs *cfg); static int pci_read_vpd_reg(device_t pcib, pcicfgregs *cfg, int reg, uint32_t *data); #if 0 static int pci_write_vpd_reg(device_t pcib, pcicfgregs *cfg, int reg, uint32_t data); #endif static void pci_read_vpd(device_t pcib, pcicfgregs *cfg); static void pci_mask_msix(device_t dev, u_int index); static void pci_unmask_msix(device_t dev, u_int index); static int pci_msi_blacklisted(void); static int pci_msix_blacklisted(void); static void pci_resume_msi(device_t dev); static void pci_resume_msix(device_t dev); static int pci_remap_intr_method(device_t bus, device_t dev, u_int irq); static uint16_t pci_get_rid_method(device_t dev, device_t child); static device_method_t pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, pci_probe), DEVMETHOD(device_attach, pci_attach), #ifdef PCI_RES_BUS DEVMETHOD(device_detach, pci_detach), #else DEVMETHOD(device_detach, bus_generic_detach), #endif DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, pci_resume), /* Bus interface */ DEVMETHOD(bus_print_child, pci_print_child), DEVMETHOD(bus_probe_nomatch, pci_probe_nomatch), DEVMETHOD(bus_read_ivar, pci_read_ivar), DEVMETHOD(bus_write_ivar, pci_write_ivar), DEVMETHOD(bus_driver_added, pci_driver_added), DEVMETHOD(bus_setup_intr, pci_setup_intr), DEVMETHOD(bus_teardown_intr, pci_teardown_intr), DEVMETHOD(bus_get_dma_tag, pci_get_dma_tag), DEVMETHOD(bus_get_resource_list,pci_get_resource_list), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_delete_resource, pci_delete_resource), DEVMETHOD(bus_alloc_resource, pci_alloc_resource), DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), DEVMETHOD(bus_release_resource, pci_release_resource), DEVMETHOD(bus_activate_resource, pci_activate_resource), DEVMETHOD(bus_deactivate_resource, pci_deactivate_resource), DEVMETHOD(bus_child_detached, pci_child_detached), DEVMETHOD(bus_child_pnpinfo_str, pci_child_pnpinfo_str_method), DEVMETHOD(bus_child_location_str, pci_child_location_str_method), DEVMETHOD(bus_remap_intr, pci_remap_intr_method), DEVMETHOD(bus_suspend_child, pci_suspend_child), DEVMETHOD(bus_resume_child, pci_resume_child), /* PCI interface */ DEVMETHOD(pci_read_config, pci_read_config_method), DEVMETHOD(pci_write_config, pci_write_config_method), DEVMETHOD(pci_enable_busmaster, pci_enable_busmaster_method), DEVMETHOD(pci_disable_busmaster, pci_disable_busmaster_method), DEVMETHOD(pci_enable_io, pci_enable_io_method), DEVMETHOD(pci_disable_io, pci_disable_io_method), DEVMETHOD(pci_get_vpd_ident, pci_get_vpd_ident_method), DEVMETHOD(pci_get_vpd_readonly, pci_get_vpd_readonly_method), DEVMETHOD(pci_get_powerstate, pci_get_powerstate_method), DEVMETHOD(pci_set_powerstate, pci_set_powerstate_method), DEVMETHOD(pci_assign_interrupt, pci_assign_interrupt_method), DEVMETHOD(pci_find_cap, pci_find_cap_method), DEVMETHOD(pci_find_extcap, pci_find_extcap_method), DEVMETHOD(pci_find_htcap, pci_find_htcap_method), DEVMETHOD(pci_alloc_msi, pci_alloc_msi_method), DEVMETHOD(pci_alloc_msix, pci_alloc_msix_method), DEVMETHOD(pci_enable_msi, pci_enable_msi_method), DEVMETHOD(pci_enable_msix, pci_enable_msix_method), DEVMETHOD(pci_disable_msi, pci_disable_msi_method), DEVMETHOD(pci_remap_msix, pci_remap_msix_method), DEVMETHOD(pci_release_msi, pci_release_msi_method), DEVMETHOD(pci_msi_count, pci_msi_count_method), DEVMETHOD(pci_msix_count, pci_msix_count_method), DEVMETHOD(pci_get_rid, pci_get_rid_method), DEVMETHOD(pci_child_added, pci_child_added_method), DEVMETHOD_END }; DEFINE_CLASS_0(pci, pci_driver, pci_methods, sizeof(struct pci_softc)); static devclass_t pci_devclass; DRIVER_MODULE(pci, pcib, pci_driver, pci_devclass, pci_modevent, NULL); MODULE_VERSION(pci, 1); static char *pci_vendordata; static size_t pci_vendordata_size; struct pci_quirk { uint32_t devid; /* Vendor/device of the card */ int type; #define PCI_QUIRK_MAP_REG 1 /* PCI map register in weird place */ #define PCI_QUIRK_DISABLE_MSI 2 /* Neither MSI nor MSI-X work */ #define PCI_QUIRK_ENABLE_MSI_VM 3 /* Older chipset in VM where MSI works */ #define PCI_QUIRK_UNMAP_REG 4 /* Ignore PCI map register */ #define PCI_QUIRK_DISABLE_MSIX 5 /* MSI-X doesn't work */ #define PCI_QUIRK_MSI_INTX_BUG 6 /* PCIM_CMD_INTxDIS disables MSI */ int arg1; int arg2; }; static const struct pci_quirk pci_quirks[] = { /* The Intel 82371AB and 82443MX have a map register at offset 0x90. */ { 0x71138086, PCI_QUIRK_MAP_REG, 0x90, 0 }, { 0x719b8086, PCI_QUIRK_MAP_REG, 0x90, 0 }, /* As does the Serverworks OSB4 (the SMBus mapping register) */ { 0x02001166, PCI_QUIRK_MAP_REG, 0x90, 0 }, /* * MSI doesn't work with the ServerWorks CNB20-HE Host Bridge * or the CMIC-SL (AKA ServerWorks GC_LE). */ { 0x00141166, PCI_QUIRK_DISABLE_MSI, 0, 0 }, { 0x00171166, PCI_QUIRK_DISABLE_MSI, 0, 0 }, /* * MSI doesn't work on earlier Intel chipsets including * E7500, E7501, E7505, 845, 865, 875/E7210, and 855. */ { 0x25408086, PCI_QUIRK_DISABLE_MSI, 0, 0 }, { 0x254c8086, PCI_QUIRK_DISABLE_MSI, 0, 0 }, { 0x25508086, PCI_QUIRK_DISABLE_MSI, 0, 0 }, { 0x25608086, PCI_QUIRK_DISABLE_MSI, 0, 0 }, { 0x25708086, PCI_QUIRK_DISABLE_MSI, 0, 0 }, { 0x25788086, PCI_QUIRK_DISABLE_MSI, 0, 0 }, { 0x35808086, PCI_QUIRK_DISABLE_MSI, 0, 0 }, /* * MSI doesn't work with devices behind the AMD 8131 HT-PCIX * bridge. */ { 0x74501022, PCI_QUIRK_DISABLE_MSI, 0, 0 }, /* * MSI-X allocation doesn't work properly for devices passed through * by VMware up to at least ESXi 5.1. */ { 0x079015ad, PCI_QUIRK_DISABLE_MSIX, 0, 0 }, /* PCI/PCI-X */ { 0x07a015ad, PCI_QUIRK_DISABLE_MSIX, 0, 0 }, /* PCIe */ /* * Some virtualization environments emulate an older chipset * but support MSI just fine. QEMU uses the Intel 82440. */ { 0x12378086, PCI_QUIRK_ENABLE_MSI_VM, 0, 0 }, /* * HPET MMIO base address may appear in Bar1 for AMD SB600 SMBus * controller depending on SoftPciRst register (PM_IO 0x55 [7]). * It prevents us from attaching hpet(4) when the bit is unset. * Note this quirk only affects SB600 revision A13 and earlier. * For SB600 A21 and later, firmware must set the bit to hide it. * For SB700 and later, it is unused and hardcoded to zero. */ { 0x43851002, PCI_QUIRK_UNMAP_REG, 0x14, 0 }, /* * Atheros AR8161/AR8162/E2200 ethernet controller has a bug that * MSI interrupt does not assert if PCIM_CMD_INTxDIS bit of the * command register is set. */ { 0x10911969, PCI_QUIRK_MSI_INTX_BUG, 0, 0 }, { 0xE0911969, PCI_QUIRK_MSI_INTX_BUG, 0, 0 }, { 0x10901969, PCI_QUIRK_MSI_INTX_BUG, 0, 0 }, { 0 } }; /* map register information */ #define PCI_MAPMEM 0x01 /* memory map */ #define PCI_MAPMEMP 0x02 /* prefetchable memory map */ #define PCI_MAPPORT 0x04 /* port map */ struct devlist pci_devq; uint32_t pci_generation; uint32_t pci_numdevs = 0; static int pcie_chipset, pcix_chipset; /* sysctl vars */ SYSCTL_NODE(_hw, OID_AUTO, pci, CTLFLAG_RD, 0, "PCI bus tuning parameters"); static int pci_enable_io_modes = 1; SYSCTL_INT(_hw_pci, OID_AUTO, enable_io_modes, CTLFLAG_RWTUN, &pci_enable_io_modes, 1, "Enable I/O and memory bits in the config register. Some BIOSes do not\n\ enable these bits correctly. We'd like to do this all the time, but there\n\ are some peripherals that this causes problems with."); static int pci_do_realloc_bars = 0; SYSCTL_INT(_hw_pci, OID_AUTO, realloc_bars, CTLFLAG_RWTUN, &pci_do_realloc_bars, 0, "Attempt to allocate a new range for any BARs whose original " "firmware-assigned ranges fail to allocate during the initial device scan."); static int pci_do_power_nodriver = 0; SYSCTL_INT(_hw_pci, OID_AUTO, do_power_nodriver, CTLFLAG_RWTUN, &pci_do_power_nodriver, 0, "Place a function into D3 state when no driver attaches to it. 0 means\n\ disable. 1 means conservatively place devices into D3 state. 2 means\n\ agressively place devices into D3 state. 3 means put absolutely everything\n\ in D3 state."); int pci_do_power_resume = 1; SYSCTL_INT(_hw_pci, OID_AUTO, do_power_resume, CTLFLAG_RWTUN, &pci_do_power_resume, 1, "Transition from D3 -> D0 on resume."); int pci_do_power_suspend = 1; SYSCTL_INT(_hw_pci, OID_AUTO, do_power_suspend, CTLFLAG_RWTUN, &pci_do_power_suspend, 1, "Transition from D0 -> D3 on suspend."); static int pci_do_msi = 1; SYSCTL_INT(_hw_pci, OID_AUTO, enable_msi, CTLFLAG_RWTUN, &pci_do_msi, 1, "Enable support for MSI interrupts"); static int pci_do_msix = 1; SYSCTL_INT(_hw_pci, OID_AUTO, enable_msix, CTLFLAG_RWTUN, &pci_do_msix, 1, "Enable support for MSI-X interrupts"); static int pci_honor_msi_blacklist = 1; SYSCTL_INT(_hw_pci, OID_AUTO, honor_msi_blacklist, CTLFLAG_RDTUN, &pci_honor_msi_blacklist, 1, "Honor chipset blacklist for MSI/MSI-X"); #if defined(__i386__) || defined(__amd64__) static int pci_usb_takeover = 1; #else static int pci_usb_takeover = 0; #endif SYSCTL_INT(_hw_pci, OID_AUTO, usb_early_takeover, CTLFLAG_RDTUN, &pci_usb_takeover, 1, "Enable early takeover of USB controllers.\n\ Disable this if you depend on BIOS emulation of USB devices, that is\n\ you use USB devices (like keyboard or mouse) but do not load USB drivers"); static int pci_clear_bars; SYSCTL_INT(_hw_pci, OID_AUTO, clear_bars, CTLFLAG_RDTUN, &pci_clear_bars, 0, "Ignore firmware-assigned resources for BARs."); #if defined(NEW_PCIB) && defined(PCI_RES_BUS) static int pci_clear_buses; SYSCTL_INT(_hw_pci, OID_AUTO, clear_buses, CTLFLAG_RDTUN, &pci_clear_buses, 0, "Ignore firmware-assigned bus numbers."); #endif static int pci_enable_ari = 1; SYSCTL_INT(_hw_pci, OID_AUTO, enable_ari, CTLFLAG_RDTUN, &pci_enable_ari, 0, "Enable support for PCIe Alternative RID Interpretation"); static int pci_has_quirk(uint32_t devid, int quirk) { const struct pci_quirk *q; for (q = &pci_quirks[0]; q->devid; q++) { if (q->devid == devid && q->type == quirk) return (1); } return (0); } /* Find a device_t by bus/slot/function in domain 0 */ device_t pci_find_bsf(uint8_t bus, uint8_t slot, uint8_t func) { return (pci_find_dbsf(0, bus, slot, func)); } /* Find a device_t by domain/bus/slot/function */ device_t pci_find_dbsf(uint32_t domain, uint8_t bus, uint8_t slot, uint8_t func) { struct pci_devinfo *dinfo; STAILQ_FOREACH(dinfo, &pci_devq, pci_links) { if ((dinfo->cfg.domain == domain) && (dinfo->cfg.bus == bus) && (dinfo->cfg.slot == slot) && (dinfo->cfg.func == func)) { return (dinfo->cfg.dev); } } return (NULL); } /* Find a device_t by vendor/device ID */ device_t pci_find_device(uint16_t vendor, uint16_t device) { struct pci_devinfo *dinfo; STAILQ_FOREACH(dinfo, &pci_devq, pci_links) { if ((dinfo->cfg.vendor == vendor) && (dinfo->cfg.device == device)) { return (dinfo->cfg.dev); } } return (NULL); } device_t pci_find_class(uint8_t class, uint8_t subclass) { struct pci_devinfo *dinfo; STAILQ_FOREACH(dinfo, &pci_devq, pci_links) { if (dinfo->cfg.baseclass == class && dinfo->cfg.subclass == subclass) { return (dinfo->cfg.dev); } } return (NULL); } static int pci_printf(pcicfgregs *cfg, const char *fmt, ...) { va_list ap; int retval; retval = printf("pci%d:%d:%d:%d: ", cfg->domain, cfg->bus, cfg->slot, cfg->func); va_start(ap, fmt); retval += vprintf(fmt, ap); va_end(ap); return (retval); } /* return base address of memory or port map */ static pci_addr_t pci_mapbase(uint64_t mapreg) { if (PCI_BAR_MEM(mapreg)) return (mapreg & PCIM_BAR_MEM_BASE); else return (mapreg & PCIM_BAR_IO_BASE); } /* return map type of memory or port map */ static const char * pci_maptype(uint64_t mapreg) { if (PCI_BAR_IO(mapreg)) return ("I/O Port"); if (mapreg & PCIM_BAR_MEM_PREFETCH) return ("Prefetchable Memory"); return ("Memory"); } /* return log2 of map size decoded for memory or port map */ static int pci_mapsize(uint64_t testval) { int ln2size; testval = pci_mapbase(testval); ln2size = 0; if (testval != 0) { while ((testval & 1) == 0) { ln2size++; testval >>= 1; } } return (ln2size); } /* return base address of device ROM */ static pci_addr_t pci_rombase(uint64_t mapreg) { return (mapreg & PCIM_BIOS_ADDR_MASK); } /* return log2 of map size decided for device ROM */ static int pci_romsize(uint64_t testval) { int ln2size; testval = pci_rombase(testval); ln2size = 0; if (testval != 0) { while ((testval & 1) == 0) { ln2size++; testval >>= 1; } } return (ln2size); } /* return log2 of address range supported by map register */ static int pci_maprange(uint64_t mapreg) { int ln2range = 0; if (PCI_BAR_IO(mapreg)) ln2range = 32; else switch (mapreg & PCIM_BAR_MEM_TYPE) { case PCIM_BAR_MEM_32: ln2range = 32; break; case PCIM_BAR_MEM_1MB: ln2range = 20; break; case PCIM_BAR_MEM_64: ln2range = 64; break; } return (ln2range); } /* adjust some values from PCI 1.0 devices to match 2.0 standards ... */ static void pci_fixancient(pcicfgregs *cfg) { if ((cfg->hdrtype & PCIM_HDRTYPE) != PCIM_HDRTYPE_NORMAL) return; /* PCI to PCI bridges use header type 1 */ if (cfg->baseclass == PCIC_BRIDGE && cfg->subclass == PCIS_BRIDGE_PCI) cfg->hdrtype = PCIM_HDRTYPE_BRIDGE; } /* extract header type specific config data */ static void pci_hdrtypedata(device_t pcib, int b, int s, int f, pcicfgregs *cfg) { #define REG(n, w) PCIB_READ_CONFIG(pcib, b, s, f, n, w) switch (cfg->hdrtype & PCIM_HDRTYPE) { case PCIM_HDRTYPE_NORMAL: cfg->subvendor = REG(PCIR_SUBVEND_0, 2); cfg->subdevice = REG(PCIR_SUBDEV_0, 2); cfg->nummaps = PCI_MAXMAPS_0; break; case PCIM_HDRTYPE_BRIDGE: cfg->nummaps = PCI_MAXMAPS_1; break; case PCIM_HDRTYPE_CARDBUS: cfg->subvendor = REG(PCIR_SUBVEND_2, 2); cfg->subdevice = REG(PCIR_SUBDEV_2, 2); cfg->nummaps = PCI_MAXMAPS_2; break; } #undef REG } /* read configuration header into pcicfgregs structure */ struct pci_devinfo * pci_read_device(device_t pcib, int d, int b, int s, int f, size_t size) { #define REG(n, w) PCIB_READ_CONFIG(pcib, b, s, f, n, w) pcicfgregs *cfg = NULL; struct pci_devinfo *devlist_entry; struct devlist *devlist_head; devlist_head = &pci_devq; devlist_entry = NULL; if (REG(PCIR_DEVVENDOR, 4) != 0xfffffffful) { devlist_entry = malloc(size, M_DEVBUF, M_WAITOK | M_ZERO); if (devlist_entry == NULL) return (NULL); cfg = &devlist_entry->cfg; cfg->domain = d; cfg->bus = b; cfg->slot = s; cfg->func = f; cfg->vendor = REG(PCIR_VENDOR, 2); cfg->device = REG(PCIR_DEVICE, 2); cfg->cmdreg = REG(PCIR_COMMAND, 2); cfg->statreg = REG(PCIR_STATUS, 2); cfg->baseclass = REG(PCIR_CLASS, 1); cfg->subclass = REG(PCIR_SUBCLASS, 1); cfg->progif = REG(PCIR_PROGIF, 1); cfg->revid = REG(PCIR_REVID, 1); cfg->hdrtype = REG(PCIR_HDRTYPE, 1); cfg->cachelnsz = REG(PCIR_CACHELNSZ, 1); cfg->lattimer = REG(PCIR_LATTIMER, 1); cfg->intpin = REG(PCIR_INTPIN, 1); cfg->intline = REG(PCIR_INTLINE, 1); cfg->mingnt = REG(PCIR_MINGNT, 1); cfg->maxlat = REG(PCIR_MAXLAT, 1); cfg->mfdev = (cfg->hdrtype & PCIM_MFDEV) != 0; cfg->hdrtype &= ~PCIM_MFDEV; STAILQ_INIT(&cfg->maps); pci_fixancient(cfg); pci_hdrtypedata(pcib, b, s, f, cfg); if (REG(PCIR_STATUS, 2) & PCIM_STATUS_CAPPRESENT) pci_read_cap(pcib, cfg); STAILQ_INSERT_TAIL(devlist_head, devlist_entry, pci_links); devlist_entry->conf.pc_sel.pc_domain = cfg->domain; devlist_entry->conf.pc_sel.pc_bus = cfg->bus; devlist_entry->conf.pc_sel.pc_dev = cfg->slot; devlist_entry->conf.pc_sel.pc_func = cfg->func; devlist_entry->conf.pc_hdr = cfg->hdrtype; devlist_entry->conf.pc_subvendor = cfg->subvendor; devlist_entry->conf.pc_subdevice = cfg->subdevice; devlist_entry->conf.pc_vendor = cfg->vendor; devlist_entry->conf.pc_device = cfg->device; devlist_entry->conf.pc_class = cfg->baseclass; devlist_entry->conf.pc_subclass = cfg->subclass; devlist_entry->conf.pc_progif = cfg->progif; devlist_entry->conf.pc_revid = cfg->revid; pci_numdevs++; pci_generation++; } return (devlist_entry); #undef REG } static void pci_read_cap(device_t pcib, pcicfgregs *cfg) { #define REG(n, w) PCIB_READ_CONFIG(pcib, cfg->bus, cfg->slot, cfg->func, n, w) #define WREG(n, v, w) PCIB_WRITE_CONFIG(pcib, cfg->bus, cfg->slot, cfg->func, n, v, w) #if defined(__i386__) || defined(__amd64__) || defined(__powerpc__) uint64_t addr; #endif uint32_t val; int ptr, nextptr, ptrptr; switch (cfg->hdrtype & PCIM_HDRTYPE) { case PCIM_HDRTYPE_NORMAL: case PCIM_HDRTYPE_BRIDGE: ptrptr = PCIR_CAP_PTR; break; case PCIM_HDRTYPE_CARDBUS: ptrptr = PCIR_CAP_PTR_2; /* cardbus capabilities ptr */ break; default: return; /* no extended capabilities support */ } nextptr = REG(ptrptr, 1); /* sanity check? */ /* * Read capability entries. */ while (nextptr != 0) { /* Sanity check */ if (nextptr > 255) { printf("illegal PCI extended capability offset %d\n", nextptr); return; } /* Find the next entry */ ptr = nextptr; nextptr = REG(ptr + PCICAP_NEXTPTR, 1); /* Process this entry */ switch (REG(ptr + PCICAP_ID, 1)) { case PCIY_PMG: /* PCI power management */ if (cfg->pp.pp_cap == 0) { cfg->pp.pp_cap = REG(ptr + PCIR_POWER_CAP, 2); cfg->pp.pp_status = ptr + PCIR_POWER_STATUS; cfg->pp.pp_bse = ptr + PCIR_POWER_BSE; if ((nextptr - ptr) > PCIR_POWER_DATA) cfg->pp.pp_data = ptr + PCIR_POWER_DATA; } break; case PCIY_HT: /* HyperTransport */ /* Determine HT-specific capability type. */ val = REG(ptr + PCIR_HT_COMMAND, 2); if ((val & 0xe000) == PCIM_HTCAP_SLAVE) cfg->ht.ht_slave = ptr; #if defined(__i386__) || defined(__amd64__) || defined(__powerpc__) switch (val & PCIM_HTCMD_CAP_MASK) { case PCIM_HTCAP_MSI_MAPPING: if (!(val & PCIM_HTCMD_MSI_FIXED)) { /* Sanity check the mapping window. */ addr = REG(ptr + PCIR_HTMSI_ADDRESS_HI, 4); addr <<= 32; addr |= REG(ptr + PCIR_HTMSI_ADDRESS_LO, 4); if (addr != MSI_INTEL_ADDR_BASE) device_printf(pcib, "HT device at pci%d:%d:%d:%d has non-default MSI window 0x%llx\n", cfg->domain, cfg->bus, cfg->slot, cfg->func, (long long)addr); } else addr = MSI_INTEL_ADDR_BASE; cfg->ht.ht_msimap = ptr; cfg->ht.ht_msictrl = val; cfg->ht.ht_msiaddr = addr; break; } #endif break; case PCIY_MSI: /* PCI MSI */ cfg->msi.msi_location = ptr; cfg->msi.msi_ctrl = REG(ptr + PCIR_MSI_CTRL, 2); cfg->msi.msi_msgnum = 1 << ((cfg->msi.msi_ctrl & PCIM_MSICTRL_MMC_MASK)>>1); break; case PCIY_MSIX: /* PCI MSI-X */ cfg->msix.msix_location = ptr; cfg->msix.msix_ctrl = REG(ptr + PCIR_MSIX_CTRL, 2); cfg->msix.msix_msgnum = (cfg->msix.msix_ctrl & PCIM_MSIXCTRL_TABLE_SIZE) + 1; val = REG(ptr + PCIR_MSIX_TABLE, 4); cfg->msix.msix_table_bar = PCIR_BAR(val & PCIM_MSIX_BIR_MASK); cfg->msix.msix_table_offset = val & ~PCIM_MSIX_BIR_MASK; val = REG(ptr + PCIR_MSIX_PBA, 4); cfg->msix.msix_pba_bar = PCIR_BAR(val & PCIM_MSIX_BIR_MASK); cfg->msix.msix_pba_offset = val & ~PCIM_MSIX_BIR_MASK; break; case PCIY_VPD: /* PCI Vital Product Data */ cfg->vpd.vpd_reg = ptr; break; case PCIY_SUBVENDOR: /* Should always be true. */ if ((cfg->hdrtype & PCIM_HDRTYPE) == PCIM_HDRTYPE_BRIDGE) { val = REG(ptr + PCIR_SUBVENDCAP_ID, 4); cfg->subvendor = val & 0xffff; cfg->subdevice = val >> 16; } break; case PCIY_PCIX: /* PCI-X */ /* * Assume we have a PCI-X chipset if we have * at least one PCI-PCI bridge with a PCI-X * capability. Note that some systems with * PCI-express or HT chipsets might match on * this check as well. */ if ((cfg->hdrtype & PCIM_HDRTYPE) == PCIM_HDRTYPE_BRIDGE) pcix_chipset = 1; cfg->pcix.pcix_location = ptr; break; case PCIY_EXPRESS: /* PCI-express */ /* * Assume we have a PCI-express chipset if we have * at least one PCI-express device. */ pcie_chipset = 1; cfg->pcie.pcie_location = ptr; val = REG(ptr + PCIER_FLAGS, 2); cfg->pcie.pcie_type = val & PCIEM_FLAGS_TYPE; break; default: break; } } #if defined(__powerpc__) /* * Enable the MSI mapping window for all HyperTransport * slaves. PCI-PCI bridges have their windows enabled via * PCIB_MAP_MSI(). */ if (cfg->ht.ht_slave != 0 && cfg->ht.ht_msimap != 0 && !(cfg->ht.ht_msictrl & PCIM_HTCMD_MSI_ENABLE)) { device_printf(pcib, "Enabling MSI window for HyperTransport slave at pci%d:%d:%d:%d\n", cfg->domain, cfg->bus, cfg->slot, cfg->func); cfg->ht.ht_msictrl |= PCIM_HTCMD_MSI_ENABLE; WREG(cfg->ht.ht_msimap + PCIR_HT_COMMAND, cfg->ht.ht_msictrl, 2); } #endif /* REG and WREG use carry through to next functions */ } /* * PCI Vital Product Data */ #define PCI_VPD_TIMEOUT 1000000 static int pci_read_vpd_reg(device_t pcib, pcicfgregs *cfg, int reg, uint32_t *data) { int count = PCI_VPD_TIMEOUT; KASSERT((reg & 3) == 0, ("VPD register must by 4 byte aligned")); WREG(cfg->vpd.vpd_reg + PCIR_VPD_ADDR, reg, 2); while ((REG(cfg->vpd.vpd_reg + PCIR_VPD_ADDR, 2) & 0x8000) != 0x8000) { if (--count < 0) return (ENXIO); DELAY(1); /* limit looping */ } *data = (REG(cfg->vpd.vpd_reg + PCIR_VPD_DATA, 4)); return (0); } #if 0 static int pci_write_vpd_reg(device_t pcib, pcicfgregs *cfg, int reg, uint32_t data) { int count = PCI_VPD_TIMEOUT; KASSERT((reg & 3) == 0, ("VPD register must by 4 byte aligned")); WREG(cfg->vpd.vpd_reg + PCIR_VPD_DATA, data, 4); WREG(cfg->vpd.vpd_reg + PCIR_VPD_ADDR, reg | 0x8000, 2); while ((REG(cfg->vpd.vpd_reg + PCIR_VPD_ADDR, 2) & 0x8000) == 0x8000) { if (--count < 0) return (ENXIO); DELAY(1); /* limit looping */ } return (0); } #endif #undef PCI_VPD_TIMEOUT struct vpd_readstate { device_t pcib; pcicfgregs *cfg; uint32_t val; int bytesinval; int off; uint8_t cksum; }; static int vpd_nextbyte(struct vpd_readstate *vrs, uint8_t *data) { uint32_t reg; uint8_t byte; if (vrs->bytesinval == 0) { if (pci_read_vpd_reg(vrs->pcib, vrs->cfg, vrs->off, ®)) return (ENXIO); vrs->val = le32toh(reg); vrs->off += 4; byte = vrs->val & 0xff; vrs->bytesinval = 3; } else { vrs->val = vrs->val >> 8; byte = vrs->val & 0xff; vrs->bytesinval--; } vrs->cksum += byte; *data = byte; return (0); } static void pci_read_vpd(device_t pcib, pcicfgregs *cfg) { struct vpd_readstate vrs; int state; int name; int remain; int i; int alloc, off; /* alloc/off for RO/W arrays */ int cksumvalid; int dflen; uint8_t byte; uint8_t byte2; /* init vpd reader */ vrs.bytesinval = 0; vrs.off = 0; vrs.pcib = pcib; vrs.cfg = cfg; vrs.cksum = 0; state = 0; name = remain = i = 0; /* shut up stupid gcc */ alloc = off = 0; /* shut up stupid gcc */ dflen = 0; /* shut up stupid gcc */ cksumvalid = -1; while (state >= 0) { if (vpd_nextbyte(&vrs, &byte)) { state = -2; break; } #if 0 printf("vpd: val: %#x, off: %d, bytesinval: %d, byte: %#hhx, " \ "state: %d, remain: %d, name: %#x, i: %d\n", vrs.val, vrs.off, vrs.bytesinval, byte, state, remain, name, i); #endif switch (state) { case 0: /* item name */ if (byte & 0x80) { if (vpd_nextbyte(&vrs, &byte2)) { state = -2; break; } remain = byte2; if (vpd_nextbyte(&vrs, &byte2)) { state = -2; break; } remain |= byte2 << 8; if (remain > (0x7f*4 - vrs.off)) { state = -1; pci_printf(cfg, "invalid VPD data, remain %#x\n", remain); } name = byte & 0x7f; } else { remain = byte & 0x7; name = (byte >> 3) & 0xf; } switch (name) { case 0x2: /* String */ cfg->vpd.vpd_ident = malloc(remain + 1, M_DEVBUF, M_WAITOK); i = 0; state = 1; break; case 0xf: /* End */ state = -1; break; case 0x10: /* VPD-R */ alloc = 8; off = 0; cfg->vpd.vpd_ros = malloc(alloc * sizeof(*cfg->vpd.vpd_ros), M_DEVBUF, M_WAITOK | M_ZERO); state = 2; break; case 0x11: /* VPD-W */ alloc = 8; off = 0; cfg->vpd.vpd_w = malloc(alloc * sizeof(*cfg->vpd.vpd_w), M_DEVBUF, M_WAITOK | M_ZERO); state = 5; break; default: /* Invalid data, abort */ state = -1; break; } break; case 1: /* Identifier String */ cfg->vpd.vpd_ident[i++] = byte; remain--; if (remain == 0) { cfg->vpd.vpd_ident[i] = '\0'; state = 0; } break; case 2: /* VPD-R Keyword Header */ if (off == alloc) { cfg->vpd.vpd_ros = reallocf(cfg->vpd.vpd_ros, (alloc *= 2) * sizeof(*cfg->vpd.vpd_ros), M_DEVBUF, M_WAITOK | M_ZERO); } cfg->vpd.vpd_ros[off].keyword[0] = byte; if (vpd_nextbyte(&vrs, &byte2)) { state = -2; break; } cfg->vpd.vpd_ros[off].keyword[1] = byte2; if (vpd_nextbyte(&vrs, &byte2)) { state = -2; break; } cfg->vpd.vpd_ros[off].len = dflen = byte2; if (dflen == 0 && strncmp(cfg->vpd.vpd_ros[off].keyword, "RV", 2) == 0) { /* * if this happens, we can't trust the rest * of the VPD. */ pci_printf(cfg, "bad keyword length: %d\n", dflen); cksumvalid = 0; state = -1; break; } else if (dflen == 0) { cfg->vpd.vpd_ros[off].value = malloc(1 * sizeof(*cfg->vpd.vpd_ros[off].value), M_DEVBUF, M_WAITOK); cfg->vpd.vpd_ros[off].value[0] = '\x00'; } else cfg->vpd.vpd_ros[off].value = malloc( (dflen + 1) * sizeof(*cfg->vpd.vpd_ros[off].value), M_DEVBUF, M_WAITOK); remain -= 3; i = 0; /* keep in sync w/ state 3's transistions */ if (dflen == 0 && remain == 0) state = 0; else if (dflen == 0) state = 2; else state = 3; break; case 3: /* VPD-R Keyword Value */ cfg->vpd.vpd_ros[off].value[i++] = byte; if (strncmp(cfg->vpd.vpd_ros[off].keyword, "RV", 2) == 0 && cksumvalid == -1) { if (vrs.cksum == 0) cksumvalid = 1; else { if (bootverbose) pci_printf(cfg, "bad VPD cksum, remain %hhu\n", vrs.cksum); cksumvalid = 0; state = -1; break; } } dflen--; remain--; /* keep in sync w/ state 2's transistions */ if (dflen == 0) cfg->vpd.vpd_ros[off++].value[i++] = '\0'; if (dflen == 0 && remain == 0) { cfg->vpd.vpd_rocnt = off; cfg->vpd.vpd_ros = reallocf(cfg->vpd.vpd_ros, off * sizeof(*cfg->vpd.vpd_ros), M_DEVBUF, M_WAITOK | M_ZERO); state = 0; } else if (dflen == 0) state = 2; break; case 4: remain--; if (remain == 0) state = 0; break; case 5: /* VPD-W Keyword Header */ if (off == alloc) { cfg->vpd.vpd_w = reallocf(cfg->vpd.vpd_w, (alloc *= 2) * sizeof(*cfg->vpd.vpd_w), M_DEVBUF, M_WAITOK | M_ZERO); } cfg->vpd.vpd_w[off].keyword[0] = byte; if (vpd_nextbyte(&vrs, &byte2)) { state = -2; break; } cfg->vpd.vpd_w[off].keyword[1] = byte2; if (vpd_nextbyte(&vrs, &byte2)) { state = -2; break; } cfg->vpd.vpd_w[off].len = dflen = byte2; cfg->vpd.vpd_w[off].start = vrs.off - vrs.bytesinval; cfg->vpd.vpd_w[off].value = malloc((dflen + 1) * sizeof(*cfg->vpd.vpd_w[off].value), M_DEVBUF, M_WAITOK); remain -= 3; i = 0; /* keep in sync w/ state 6's transistions */ if (dflen == 0 && remain == 0) state = 0; else if (dflen == 0) state = 5; else state = 6; break; case 6: /* VPD-W Keyword Value */ cfg->vpd.vpd_w[off].value[i++] = byte; dflen--; remain--; /* keep in sync w/ state 5's transistions */ if (dflen == 0) cfg->vpd.vpd_w[off++].value[i++] = '\0'; if (dflen == 0 && remain == 0) { cfg->vpd.vpd_wcnt = off; cfg->vpd.vpd_w = reallocf(cfg->vpd.vpd_w, off * sizeof(*cfg->vpd.vpd_w), M_DEVBUF, M_WAITOK | M_ZERO); state = 0; } else if (dflen == 0) state = 5; break; default: pci_printf(cfg, "invalid state: %d\n", state); state = -1; break; } } if (cksumvalid == 0 || state < -1) { /* read-only data bad, clean up */ if (cfg->vpd.vpd_ros != NULL) { for (off = 0; cfg->vpd.vpd_ros[off].value; off++) free(cfg->vpd.vpd_ros[off].value, M_DEVBUF); free(cfg->vpd.vpd_ros, M_DEVBUF); cfg->vpd.vpd_ros = NULL; } } if (state < -1) { /* I/O error, clean up */ pci_printf(cfg, "failed to read VPD data.\n"); if (cfg->vpd.vpd_ident != NULL) { free(cfg->vpd.vpd_ident, M_DEVBUF); cfg->vpd.vpd_ident = NULL; } if (cfg->vpd.vpd_w != NULL) { for (off = 0; cfg->vpd.vpd_w[off].value; off++) free(cfg->vpd.vpd_w[off].value, M_DEVBUF); free(cfg->vpd.vpd_w, M_DEVBUF); cfg->vpd.vpd_w = NULL; } } cfg->vpd.vpd_cached = 1; #undef REG #undef WREG } int pci_get_vpd_ident_method(device_t dev, device_t child, const char **identptr) { struct pci_devinfo *dinfo = device_get_ivars(child); pcicfgregs *cfg = &dinfo->cfg; if (!cfg->vpd.vpd_cached && cfg->vpd.vpd_reg != 0) pci_read_vpd(device_get_parent(dev), cfg); *identptr = cfg->vpd.vpd_ident; if (*identptr == NULL) return (ENXIO); return (0); } int pci_get_vpd_readonly_method(device_t dev, device_t child, const char *kw, const char **vptr) { struct pci_devinfo *dinfo = device_get_ivars(child); pcicfgregs *cfg = &dinfo->cfg; int i; if (!cfg->vpd.vpd_cached && cfg->vpd.vpd_reg != 0) pci_read_vpd(device_get_parent(dev), cfg); for (i = 0; i < cfg->vpd.vpd_rocnt; i++) if (memcmp(kw, cfg->vpd.vpd_ros[i].keyword, sizeof(cfg->vpd.vpd_ros[i].keyword)) == 0) { *vptr = cfg->vpd.vpd_ros[i].value; return (0); } *vptr = NULL; return (ENXIO); } struct pcicfg_vpd * pci_fetch_vpd_list(device_t dev) { struct pci_devinfo *dinfo = device_get_ivars(dev); pcicfgregs *cfg = &dinfo->cfg; if (!cfg->vpd.vpd_cached && cfg->vpd.vpd_reg != 0) pci_read_vpd(device_get_parent(device_get_parent(dev)), cfg); return (&cfg->vpd); } /* * Find the requested HyperTransport capability and return the offset * in configuration space via the pointer provided. The function * returns 0 on success and an error code otherwise. */ int pci_find_htcap_method(device_t dev, device_t child, int capability, int *capreg) { int ptr, error; uint16_t val; error = pci_find_cap(child, PCIY_HT, &ptr); if (error) return (error); /* * Traverse the capabilities list checking each HT capability * to see if it matches the requested HT capability. */ while (ptr != 0) { val = pci_read_config(child, ptr + PCIR_HT_COMMAND, 2); if (capability == PCIM_HTCAP_SLAVE || capability == PCIM_HTCAP_HOST) val &= 0xe000; else val &= PCIM_HTCMD_CAP_MASK; if (val == capability) { if (capreg != NULL) *capreg = ptr; return (0); } /* Skip to the next HT capability. */ while (ptr != 0) { ptr = pci_read_config(child, ptr + PCICAP_NEXTPTR, 1); if (pci_read_config(child, ptr + PCICAP_ID, 1) == PCIY_HT) break; } } return (ENOENT); } /* * Find the requested capability and return the offset in * configuration space via the pointer provided. The function returns * 0 on success and an error code otherwise. */ int pci_find_cap_method(device_t dev, device_t child, int capability, int *capreg) { struct pci_devinfo *dinfo = device_get_ivars(child); pcicfgregs *cfg = &dinfo->cfg; u_int32_t status; u_int8_t ptr; /* * Check the CAP_LIST bit of the PCI status register first. */ status = pci_read_config(child, PCIR_STATUS, 2); if (!(status & PCIM_STATUS_CAPPRESENT)) return (ENXIO); /* * Determine the start pointer of the capabilities list. */ switch (cfg->hdrtype & PCIM_HDRTYPE) { case PCIM_HDRTYPE_NORMAL: case PCIM_HDRTYPE_BRIDGE: ptr = PCIR_CAP_PTR; break; case PCIM_HDRTYPE_CARDBUS: ptr = PCIR_CAP_PTR_2; break; default: /* XXX: panic? */ return (ENXIO); /* no extended capabilities support */ } ptr = pci_read_config(child, ptr, 1); /* * Traverse the capabilities list. */ while (ptr != 0) { if (pci_read_config(child, ptr + PCICAP_ID, 1) == capability) { if (capreg != NULL) *capreg = ptr; return (0); } ptr = pci_read_config(child, ptr + PCICAP_NEXTPTR, 1); } return (ENOENT); } /* * Find the requested extended capability and return the offset in * configuration space via the pointer provided. The function returns * 0 on success and an error code otherwise. */ int pci_find_extcap_method(device_t dev, device_t child, int capability, int *capreg) { struct pci_devinfo *dinfo = device_get_ivars(child); pcicfgregs *cfg = &dinfo->cfg; uint32_t ecap; uint16_t ptr; /* Only supported for PCI-express devices. */ if (cfg->pcie.pcie_location == 0) return (ENXIO); ptr = PCIR_EXTCAP; ecap = pci_read_config(child, ptr, 4); if (ecap == 0xffffffff || ecap == 0) return (ENOENT); for (;;) { if (PCI_EXTCAP_ID(ecap) == capability) { if (capreg != NULL) *capreg = ptr; return (0); } ptr = PCI_EXTCAP_NEXTPTR(ecap); if (ptr == 0) break; ecap = pci_read_config(child, ptr, 4); } return (ENOENT); } /* * Support for MSI-X message interrupts. */ void pci_enable_msix_method(device_t dev, device_t child, u_int index, uint64_t address, uint32_t data) { struct pci_devinfo *dinfo = device_get_ivars(child); struct pcicfg_msix *msix = &dinfo->cfg.msix; uint32_t offset; KASSERT(msix->msix_table_len > index, ("bogus index")); offset = msix->msix_table_offset + index * 16; bus_write_4(msix->msix_table_res, offset, address & 0xffffffff); bus_write_4(msix->msix_table_res, offset + 4, address >> 32); bus_write_4(msix->msix_table_res, offset + 8, data); /* Enable MSI -> HT mapping. */ pci_ht_map_msi(child, address); } void pci_mask_msix(device_t dev, u_int index) { struct pci_devinfo *dinfo = device_get_ivars(dev); struct pcicfg_msix *msix = &dinfo->cfg.msix; uint32_t offset, val; KASSERT(msix->msix_msgnum > index, ("bogus index")); offset = msix->msix_table_offset + index * 16 + 12; val = bus_read_4(msix->msix_table_res, offset); if (!(val & PCIM_MSIX_VCTRL_MASK)) { val |= PCIM_MSIX_VCTRL_MASK; bus_write_4(msix->msix_table_res, offset, val); } } void pci_unmask_msix(device_t dev, u_int index) { struct pci_devinfo *dinfo = device_get_ivars(dev); struct pcicfg_msix *msix = &dinfo->cfg.msix; uint32_t offset, val; KASSERT(msix->msix_table_len > index, ("bogus index")); offset = msix->msix_table_offset + index * 16 + 12; val = bus_read_4(msix->msix_table_res, offset); if (val & PCIM_MSIX_VCTRL_MASK) { val &= ~PCIM_MSIX_VCTRL_MASK; bus_write_4(msix->msix_table_res, offset, val); } } int pci_pending_msix(device_t dev, u_int index) { struct pci_devinfo *dinfo = device_get_ivars(dev); struct pcicfg_msix *msix = &dinfo->cfg.msix; uint32_t offset, bit; KASSERT(msix->msix_table_len > index, ("bogus index")); offset = msix->msix_pba_offset + (index / 32) * 4; bit = 1 << index % 32; return (bus_read_4(msix->msix_pba_res, offset) & bit); } /* * Restore MSI-X registers and table during resume. If MSI-X is * enabled then walk the virtual table to restore the actual MSI-X * table. */ static void pci_resume_msix(device_t dev) { struct pci_devinfo *dinfo = device_get_ivars(dev); struct pcicfg_msix *msix = &dinfo->cfg.msix; struct msix_table_entry *mte; struct msix_vector *mv; int i; if (msix->msix_alloc > 0) { /* First, mask all vectors. */ for (i = 0; i < msix->msix_msgnum; i++) pci_mask_msix(dev, i); /* Second, program any messages with at least one handler. */ for (i = 0; i < msix->msix_table_len; i++) { mte = &msix->msix_table[i]; if (mte->mte_vector == 0 || mte->mte_handlers == 0) continue; mv = &msix->msix_vectors[mte->mte_vector - 1]; pci_enable_msix(dev, i, mv->mv_address, mv->mv_data); pci_unmask_msix(dev, i); } } pci_write_config(dev, msix->msix_location + PCIR_MSIX_CTRL, msix->msix_ctrl, 2); } /* * Attempt to allocate *count MSI-X messages. The actual number allocated is * returned in *count. After this function returns, each message will be * available to the driver as SYS_RES_IRQ resources starting at rid 1. */ int pci_alloc_msix_method(device_t dev, device_t child, int *count) { struct pci_devinfo *dinfo = device_get_ivars(child); pcicfgregs *cfg = &dinfo->cfg; struct resource_list_entry *rle; int actual, error, i, irq, max; /* Don't let count == 0 get us into trouble. */ if (*count == 0) return (EINVAL); /* If rid 0 is allocated, then fail. */ rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, 0); if (rle != NULL && rle->res != NULL) return (ENXIO); /* Already have allocated messages? */ if (cfg->msi.msi_alloc != 0 || cfg->msix.msix_alloc != 0) return (ENXIO); /* If MSI-X is blacklisted for this system, fail. */ if (pci_msix_blacklisted()) return (ENXIO); /* MSI-X capability present? */ if (cfg->msix.msix_location == 0 || !pci_do_msix) return (ENODEV); /* Make sure the appropriate BARs are mapped. */ rle = resource_list_find(&dinfo->resources, SYS_RES_MEMORY, cfg->msix.msix_table_bar); if (rle == NULL || rle->res == NULL || !(rman_get_flags(rle->res) & RF_ACTIVE)) return (ENXIO); cfg->msix.msix_table_res = rle->res; if (cfg->msix.msix_pba_bar != cfg->msix.msix_table_bar) { rle = resource_list_find(&dinfo->resources, SYS_RES_MEMORY, cfg->msix.msix_pba_bar); if (rle == NULL || rle->res == NULL || !(rman_get_flags(rle->res) & RF_ACTIVE)) return (ENXIO); } cfg->msix.msix_pba_res = rle->res; if (bootverbose) device_printf(child, "attempting to allocate %d MSI-X vectors (%d supported)\n", *count, cfg->msix.msix_msgnum); max = min(*count, cfg->msix.msix_msgnum); for (i = 0; i < max; i++) { /* Allocate a message. */ error = PCIB_ALLOC_MSIX(device_get_parent(dev), child, &irq); if (error) { if (i == 0) return (error); break; } resource_list_add(&dinfo->resources, SYS_RES_IRQ, i + 1, irq, irq, 1); } actual = i; if (bootverbose) { rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, 1); if (actual == 1) device_printf(child, "using IRQ %lu for MSI-X\n", rle->start); else { int run; /* * Be fancy and try to print contiguous runs of * IRQ values as ranges. 'irq' is the previous IRQ. * 'run' is true if we are in a range. */ device_printf(child, "using IRQs %lu", rle->start); irq = rle->start; run = 0; for (i = 1; i < actual; i++) { rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, i + 1); /* Still in a run? */ if (rle->start == irq + 1) { run = 1; irq++; continue; } /* Finish previous range. */ if (run) { printf("-%d", irq); run = 0; } /* Start new range. */ printf(",%lu", rle->start); irq = rle->start; } /* Unfinished range? */ if (run) printf("-%d", irq); printf(" for MSI-X\n"); } } /* Mask all vectors. */ for (i = 0; i < cfg->msix.msix_msgnum; i++) pci_mask_msix(child, i); /* Allocate and initialize vector data and virtual table. */ cfg->msix.msix_vectors = malloc(sizeof(struct msix_vector) * actual, M_DEVBUF, M_WAITOK | M_ZERO); cfg->msix.msix_table = malloc(sizeof(struct msix_table_entry) * actual, M_DEVBUF, M_WAITOK | M_ZERO); for (i = 0; i < actual; i++) { rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, i + 1); cfg->msix.msix_vectors[i].mv_irq = rle->start; cfg->msix.msix_table[i].mte_vector = i + 1; } /* Update control register to enable MSI-X. */ cfg->msix.msix_ctrl |= PCIM_MSIXCTRL_MSIX_ENABLE; pci_write_config(child, cfg->msix.msix_location + PCIR_MSIX_CTRL, cfg->msix.msix_ctrl, 2); /* Update counts of alloc'd messages. */ cfg->msix.msix_alloc = actual; cfg->msix.msix_table_len = actual; *count = actual; return (0); } /* * By default, pci_alloc_msix() will assign the allocated IRQ * resources consecutively to the first N messages in the MSI-X table. * However, device drivers may want to use different layouts if they * either receive fewer messages than they asked for, or they wish to * populate the MSI-X table sparsely. This method allows the driver * to specify what layout it wants. It must be called after a * successful pci_alloc_msix() but before any of the associated * SYS_RES_IRQ resources are allocated via bus_alloc_resource(). * * The 'vectors' array contains 'count' message vectors. The array * maps directly to the MSI-X table in that index 0 in the array * specifies the vector for the first message in the MSI-X table, etc. * The vector value in each array index can either be 0 to indicate * that no vector should be assigned to a message slot, or it can be a * number from 1 to N (where N is the count returned from a * succcessful call to pci_alloc_msix()) to indicate which message * vector (IRQ) to be used for the corresponding message. * * On successful return, each message with a non-zero vector will have * an associated SYS_RES_IRQ whose rid is equal to the array index + * 1. Additionally, if any of the IRQs allocated via the previous * call to pci_alloc_msix() are not used in the mapping, those IRQs * will be freed back to the system automatically. * * For example, suppose a driver has a MSI-X table with 6 messages and * asks for 6 messages, but pci_alloc_msix() only returns a count of * 3. Call the three vectors allocated by pci_alloc_msix() A, B, and * C. After the call to pci_alloc_msix(), the device will be setup to * have an MSI-X table of ABC--- (where - means no vector assigned). * If the driver then passes a vector array of { 1, 0, 1, 2, 0, 2 }, * then the MSI-X table will look like A-AB-B, and the 'C' vector will * be freed back to the system. This device will also have valid * SYS_RES_IRQ rids of 1, 3, 4, and 6. * * In any case, the SYS_RES_IRQ rid X will always map to the message * at MSI-X table index X - 1 and will only be valid if a vector is * assigned to that table entry. */ int pci_remap_msix_method(device_t dev, device_t child, int count, const u_int *vectors) { struct pci_devinfo *dinfo = device_get_ivars(child); struct pcicfg_msix *msix = &dinfo->cfg.msix; struct resource_list_entry *rle; int i, irq, j, *used; /* * Have to have at least one message in the table but the * table can't be bigger than the actual MSI-X table in the * device. */ if (count == 0 || count > msix->msix_msgnum) return (EINVAL); /* Sanity check the vectors. */ for (i = 0; i < count; i++) if (vectors[i] > msix->msix_alloc) return (EINVAL); /* * Make sure there aren't any holes in the vectors to be used. * It's a big pain to support it, and it doesn't really make * sense anyway. Also, at least one vector must be used. */ used = malloc(sizeof(int) * msix->msix_alloc, M_DEVBUF, M_WAITOK | M_ZERO); for (i = 0; i < count; i++) if (vectors[i] != 0) used[vectors[i] - 1] = 1; for (i = 0; i < msix->msix_alloc - 1; i++) if (used[i] == 0 && used[i + 1] == 1) { free(used, M_DEVBUF); return (EINVAL); } if (used[0] != 1) { free(used, M_DEVBUF); return (EINVAL); } /* Make sure none of the resources are allocated. */ for (i = 0; i < msix->msix_table_len; i++) { if (msix->msix_table[i].mte_vector == 0) continue; if (msix->msix_table[i].mte_handlers > 0) return (EBUSY); rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, i + 1); KASSERT(rle != NULL, ("missing resource")); if (rle->res != NULL) return (EBUSY); } /* Free the existing resource list entries. */ for (i = 0; i < msix->msix_table_len; i++) { if (msix->msix_table[i].mte_vector == 0) continue; resource_list_delete(&dinfo->resources, SYS_RES_IRQ, i + 1); } /* * Build the new virtual table keeping track of which vectors are * used. */ free(msix->msix_table, M_DEVBUF); msix->msix_table = malloc(sizeof(struct msix_table_entry) * count, M_DEVBUF, M_WAITOK | M_ZERO); for (i = 0; i < count; i++) msix->msix_table[i].mte_vector = vectors[i]; msix->msix_table_len = count; /* Free any unused IRQs and resize the vectors array if necessary. */ j = msix->msix_alloc - 1; if (used[j] == 0) { struct msix_vector *vec; while (used[j] == 0) { PCIB_RELEASE_MSIX(device_get_parent(dev), child, msix->msix_vectors[j].mv_irq); j--; } vec = malloc(sizeof(struct msix_vector) * (j + 1), M_DEVBUF, M_WAITOK); bcopy(msix->msix_vectors, vec, sizeof(struct msix_vector) * (j + 1)); free(msix->msix_vectors, M_DEVBUF); msix->msix_vectors = vec; msix->msix_alloc = j + 1; } free(used, M_DEVBUF); /* Map the IRQs onto the rids. */ for (i = 0; i < count; i++) { if (vectors[i] == 0) continue; irq = msix->msix_vectors[vectors[i]].mv_irq; resource_list_add(&dinfo->resources, SYS_RES_IRQ, i + 1, irq, irq, 1); } if (bootverbose) { device_printf(child, "Remapped MSI-X IRQs as: "); for (i = 0; i < count; i++) { if (i != 0) printf(", "); if (vectors[i] == 0) printf("---"); else printf("%d", msix->msix_vectors[vectors[i]].mv_irq); } printf("\n"); } return (0); } static int pci_release_msix(device_t dev, device_t child) { struct pci_devinfo *dinfo = device_get_ivars(child); struct pcicfg_msix *msix = &dinfo->cfg.msix; struct resource_list_entry *rle; int i; /* Do we have any messages to release? */ if (msix->msix_alloc == 0) return (ENODEV); /* Make sure none of the resources are allocated. */ for (i = 0; i < msix->msix_table_len; i++) { if (msix->msix_table[i].mte_vector == 0) continue; if (msix->msix_table[i].mte_handlers > 0) return (EBUSY); rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, i + 1); KASSERT(rle != NULL, ("missing resource")); if (rle->res != NULL) return (EBUSY); } /* Update control register to disable MSI-X. */ msix->msix_ctrl &= ~PCIM_MSIXCTRL_MSIX_ENABLE; pci_write_config(child, msix->msix_location + PCIR_MSIX_CTRL, msix->msix_ctrl, 2); /* Free the resource list entries. */ for (i = 0; i < msix->msix_table_len; i++) { if (msix->msix_table[i].mte_vector == 0) continue; resource_list_delete(&dinfo->resources, SYS_RES_IRQ, i + 1); } free(msix->msix_table, M_DEVBUF); msix->msix_table_len = 0; /* Release the IRQs. */ for (i = 0; i < msix->msix_alloc; i++) PCIB_RELEASE_MSIX(device_get_parent(dev), child, msix->msix_vectors[i].mv_irq); free(msix->msix_vectors, M_DEVBUF); msix->msix_alloc = 0; return (0); } /* * Return the max supported MSI-X messages this device supports. * Basically, assuming the MD code can alloc messages, this function * should return the maximum value that pci_alloc_msix() can return. * Thus, it is subject to the tunables, etc. */ int pci_msix_count_method(device_t dev, device_t child) { struct pci_devinfo *dinfo = device_get_ivars(child); struct pcicfg_msix *msix = &dinfo->cfg.msix; if (pci_do_msix && msix->msix_location != 0) return (msix->msix_msgnum); return (0); } /* * HyperTransport MSI mapping control */ void pci_ht_map_msi(device_t dev, uint64_t addr) { struct pci_devinfo *dinfo = device_get_ivars(dev); struct pcicfg_ht *ht = &dinfo->cfg.ht; if (!ht->ht_msimap) return; if (addr && !(ht->ht_msictrl & PCIM_HTCMD_MSI_ENABLE) && ht->ht_msiaddr >> 20 == addr >> 20) { /* Enable MSI -> HT mapping. */ ht->ht_msictrl |= PCIM_HTCMD_MSI_ENABLE; pci_write_config(dev, ht->ht_msimap + PCIR_HT_COMMAND, ht->ht_msictrl, 2); } if (!addr && ht->ht_msictrl & PCIM_HTCMD_MSI_ENABLE) { /* Disable MSI -> HT mapping. */ ht->ht_msictrl &= ~PCIM_HTCMD_MSI_ENABLE; pci_write_config(dev, ht->ht_msimap + PCIR_HT_COMMAND, ht->ht_msictrl, 2); } } int pci_get_max_read_req(device_t dev) { struct pci_devinfo *dinfo = device_get_ivars(dev); int cap; uint16_t val; cap = dinfo->cfg.pcie.pcie_location; if (cap == 0) return (0); val = pci_read_config(dev, cap + PCIER_DEVICE_CTL, 2); val &= PCIEM_CTL_MAX_READ_REQUEST; val >>= 12; return (1 << (val + 7)); } int pci_set_max_read_req(device_t dev, int size) { struct pci_devinfo *dinfo = device_get_ivars(dev); int cap; uint16_t val; cap = dinfo->cfg.pcie.pcie_location; if (cap == 0) return (0); if (size < 128) size = 128; if (size > 4096) size = 4096; size = (1 << (fls(size) - 1)); val = pci_read_config(dev, cap + PCIER_DEVICE_CTL, 2); val &= ~PCIEM_CTL_MAX_READ_REQUEST; val |= (fls(size) - 8) << 12; pci_write_config(dev, cap + PCIER_DEVICE_CTL, val, 2); return (size); } /* * Support for MSI message signalled interrupts. */ void pci_enable_msi_method(device_t dev, device_t child, uint64_t address, uint16_t data) { struct pci_devinfo *dinfo = device_get_ivars(child); struct pcicfg_msi *msi = &dinfo->cfg.msi; /* Write data and address values. */ pci_write_config(child, msi->msi_location + PCIR_MSI_ADDR, address & 0xffffffff, 4); if (msi->msi_ctrl & PCIM_MSICTRL_64BIT) { pci_write_config(child, msi->msi_location + PCIR_MSI_ADDR_HIGH, address >> 32, 4); pci_write_config(child, msi->msi_location + PCIR_MSI_DATA_64BIT, data, 2); } else pci_write_config(child, msi->msi_location + PCIR_MSI_DATA, data, 2); /* Enable MSI in the control register. */ msi->msi_ctrl |= PCIM_MSICTRL_MSI_ENABLE; pci_write_config(child, msi->msi_location + PCIR_MSI_CTRL, msi->msi_ctrl, 2); /* Enable MSI -> HT mapping. */ pci_ht_map_msi(child, address); } void pci_disable_msi_method(device_t dev, device_t child) { struct pci_devinfo *dinfo = device_get_ivars(child); struct pcicfg_msi *msi = &dinfo->cfg.msi; /* Disable MSI -> HT mapping. */ pci_ht_map_msi(child, 0); /* Disable MSI in the control register. */ msi->msi_ctrl &= ~PCIM_MSICTRL_MSI_ENABLE; pci_write_config(child, msi->msi_location + PCIR_MSI_CTRL, msi->msi_ctrl, 2); } /* * Restore MSI registers during resume. If MSI is enabled then * restore the data and address registers in addition to the control * register. */ static void pci_resume_msi(device_t dev) { struct pci_devinfo *dinfo = device_get_ivars(dev); struct pcicfg_msi *msi = &dinfo->cfg.msi; uint64_t address; uint16_t data; if (msi->msi_ctrl & PCIM_MSICTRL_MSI_ENABLE) { address = msi->msi_addr; data = msi->msi_data; pci_write_config(dev, msi->msi_location + PCIR_MSI_ADDR, address & 0xffffffff, 4); if (msi->msi_ctrl & PCIM_MSICTRL_64BIT) { pci_write_config(dev, msi->msi_location + PCIR_MSI_ADDR_HIGH, address >> 32, 4); pci_write_config(dev, msi->msi_location + PCIR_MSI_DATA_64BIT, data, 2); } else pci_write_config(dev, msi->msi_location + PCIR_MSI_DATA, data, 2); } pci_write_config(dev, msi->msi_location + PCIR_MSI_CTRL, msi->msi_ctrl, 2); } static int pci_remap_intr_method(device_t bus, device_t dev, u_int irq) { struct pci_devinfo *dinfo = device_get_ivars(dev); pcicfgregs *cfg = &dinfo->cfg; struct resource_list_entry *rle; struct msix_table_entry *mte; struct msix_vector *mv; uint64_t addr; uint32_t data; int error, i, j; /* * Handle MSI first. We try to find this IRQ among our list * of MSI IRQs. If we find it, we request updated address and * data registers and apply the results. */ if (cfg->msi.msi_alloc > 0) { /* If we don't have any active handlers, nothing to do. */ if (cfg->msi.msi_handlers == 0) return (0); for (i = 0; i < cfg->msi.msi_alloc; i++) { rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, i + 1); if (rle->start == irq) { error = PCIB_MAP_MSI(device_get_parent(bus), dev, irq, &addr, &data); if (error) return (error); pci_disable_msi(dev); dinfo->cfg.msi.msi_addr = addr; dinfo->cfg.msi.msi_data = data; pci_enable_msi(dev, addr, data); return (0); } } return (ENOENT); } /* * For MSI-X, we check to see if we have this IRQ. If we do, * we request the updated mapping info. If that works, we go * through all the slots that use this IRQ and update them. */ if (cfg->msix.msix_alloc > 0) { for (i = 0; i < cfg->msix.msix_alloc; i++) { mv = &cfg->msix.msix_vectors[i]; if (mv->mv_irq == irq) { error = PCIB_MAP_MSI(device_get_parent(bus), dev, irq, &addr, &data); if (error) return (error); mv->mv_address = addr; mv->mv_data = data; for (j = 0; j < cfg->msix.msix_table_len; j++) { mte = &cfg->msix.msix_table[j]; if (mte->mte_vector != i + 1) continue; if (mte->mte_handlers == 0) continue; pci_mask_msix(dev, j); pci_enable_msix(dev, j, addr, data); pci_unmask_msix(dev, j); } } } return (ENOENT); } return (ENOENT); } /* * Returns true if the specified device is blacklisted because MSI * doesn't work. */ int pci_msi_device_blacklisted(device_t dev) { if (!pci_honor_msi_blacklist) return (0); return (pci_has_quirk(pci_get_devid(dev), PCI_QUIRK_DISABLE_MSI)); } /* * Determine if MSI is blacklisted globally on this system. Currently, * we just check for blacklisted chipsets as represented by the * host-PCI bridge at device 0:0:0. In the future, it may become * necessary to check other system attributes, such as the kenv values * that give the motherboard manufacturer and model number. */ static int pci_msi_blacklisted(void) { device_t dev; if (!pci_honor_msi_blacklist) return (0); /* Blacklist all non-PCI-express and non-PCI-X chipsets. */ if (!(pcie_chipset || pcix_chipset)) { if (vm_guest != VM_GUEST_NO) { /* * Whitelist older chipsets in virtual * machines known to support MSI. */ dev = pci_find_bsf(0, 0, 0); if (dev != NULL) return (!pci_has_quirk(pci_get_devid(dev), PCI_QUIRK_ENABLE_MSI_VM)); } return (1); } dev = pci_find_bsf(0, 0, 0); if (dev != NULL) return (pci_msi_device_blacklisted(dev)); return (0); } /* * Returns true if the specified device is blacklisted because MSI-X * doesn't work. Note that this assumes that if MSI doesn't work, * MSI-X doesn't either. */ int pci_msix_device_blacklisted(device_t dev) { if (!pci_honor_msi_blacklist) return (0); if (pci_has_quirk(pci_get_devid(dev), PCI_QUIRK_DISABLE_MSIX)) return (1); return (pci_msi_device_blacklisted(dev)); } /* * Determine if MSI-X is blacklisted globally on this system. If MSI * is blacklisted, assume that MSI-X is as well. Check for additional * chipsets where MSI works but MSI-X does not. */ static int pci_msix_blacklisted(void) { device_t dev; if (!pci_honor_msi_blacklist) return (0); dev = pci_find_bsf(0, 0, 0); if (dev != NULL && pci_has_quirk(pci_get_devid(dev), PCI_QUIRK_DISABLE_MSIX)) return (1); return (pci_msi_blacklisted()); } /* * Attempt to allocate *count MSI messages. The actual number allocated is * returned in *count. After this function returns, each message will be * available to the driver as SYS_RES_IRQ resources starting at a rid 1. */ int pci_alloc_msi_method(device_t dev, device_t child, int *count) { struct pci_devinfo *dinfo = device_get_ivars(child); pcicfgregs *cfg = &dinfo->cfg; struct resource_list_entry *rle; int actual, error, i, irqs[32]; uint16_t ctrl; /* Don't let count == 0 get us into trouble. */ if (*count == 0) return (EINVAL); /* If rid 0 is allocated, then fail. */ rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, 0); if (rle != NULL && rle->res != NULL) return (ENXIO); /* Already have allocated messages? */ if (cfg->msi.msi_alloc != 0 || cfg->msix.msix_alloc != 0) return (ENXIO); /* If MSI is blacklisted for this system, fail. */ if (pci_msi_blacklisted()) return (ENXIO); /* MSI capability present? */ if (cfg->msi.msi_location == 0 || !pci_do_msi) return (ENODEV); if (bootverbose) device_printf(child, "attempting to allocate %d MSI vectors (%d supported)\n", *count, cfg->msi.msi_msgnum); /* Don't ask for more than the device supports. */ actual = min(*count, cfg->msi.msi_msgnum); /* Don't ask for more than 32 messages. */ actual = min(actual, 32); /* MSI requires power of 2 number of messages. */ if (!powerof2(actual)) return (EINVAL); for (;;) { /* Try to allocate N messages. */ error = PCIB_ALLOC_MSI(device_get_parent(dev), child, actual, actual, irqs); if (error == 0) break; if (actual == 1) return (error); /* Try N / 2. */ actual >>= 1; } /* * We now have N actual messages mapped onto SYS_RES_IRQ * resources in the irqs[] array, so add new resources * starting at rid 1. */ for (i = 0; i < actual; i++) resource_list_add(&dinfo->resources, SYS_RES_IRQ, i + 1, irqs[i], irqs[i], 1); if (bootverbose) { if (actual == 1) device_printf(child, "using IRQ %d for MSI\n", irqs[0]); else { int run; /* * Be fancy and try to print contiguous runs * of IRQ values as ranges. 'run' is true if * we are in a range. */ device_printf(child, "using IRQs %d", irqs[0]); run = 0; for (i = 1; i < actual; i++) { /* Still in a run? */ if (irqs[i] == irqs[i - 1] + 1) { run = 1; continue; } /* Finish previous range. */ if (run) { printf("-%d", irqs[i - 1]); run = 0; } /* Start new range. */ printf(",%d", irqs[i]); } /* Unfinished range? */ if (run) printf("-%d", irqs[actual - 1]); printf(" for MSI\n"); } } /* Update control register with actual count. */ ctrl = cfg->msi.msi_ctrl; ctrl &= ~PCIM_MSICTRL_MME_MASK; ctrl |= (ffs(actual) - 1) << 4; cfg->msi.msi_ctrl = ctrl; pci_write_config(child, cfg->msi.msi_location + PCIR_MSI_CTRL, ctrl, 2); /* Update counts of alloc'd messages. */ cfg->msi.msi_alloc = actual; cfg->msi.msi_handlers = 0; *count = actual; return (0); } /* Release the MSI messages associated with this device. */ int pci_release_msi_method(device_t dev, device_t child) { struct pci_devinfo *dinfo = device_get_ivars(child); struct pcicfg_msi *msi = &dinfo->cfg.msi; struct resource_list_entry *rle; int error, i, irqs[32]; /* Try MSI-X first. */ error = pci_release_msix(dev, child); if (error != ENODEV) return (error); /* Do we have any messages to release? */ if (msi->msi_alloc == 0) return (ENODEV); KASSERT(msi->msi_alloc <= 32, ("more than 32 alloc'd messages")); /* Make sure none of the resources are allocated. */ if (msi->msi_handlers > 0) return (EBUSY); for (i = 0; i < msi->msi_alloc; i++) { rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, i + 1); KASSERT(rle != NULL, ("missing MSI resource")); if (rle->res != NULL) return (EBUSY); irqs[i] = rle->start; } /* Update control register with 0 count. */ KASSERT(!(msi->msi_ctrl & PCIM_MSICTRL_MSI_ENABLE), ("%s: MSI still enabled", __func__)); msi->msi_ctrl &= ~PCIM_MSICTRL_MME_MASK; pci_write_config(child, msi->msi_location + PCIR_MSI_CTRL, msi->msi_ctrl, 2); /* Release the messages. */ PCIB_RELEASE_MSI(device_get_parent(dev), child, msi->msi_alloc, irqs); for (i = 0; i < msi->msi_alloc; i++) resource_list_delete(&dinfo->resources, SYS_RES_IRQ, i + 1); /* Update alloc count. */ msi->msi_alloc = 0; msi->msi_addr = 0; msi->msi_data = 0; return (0); } /* * Return the max supported MSI messages this device supports. * Basically, assuming the MD code can alloc messages, this function * should return the maximum value that pci_alloc_msi() can return. * Thus, it is subject to the tunables, etc. */ int pci_msi_count_method(device_t dev, device_t child) { struct pci_devinfo *dinfo = device_get_ivars(child); struct pcicfg_msi *msi = &dinfo->cfg.msi; if (pci_do_msi && msi->msi_location != 0) return (msi->msi_msgnum); return (0); } /* free pcicfgregs structure and all depending data structures */ int pci_freecfg(struct pci_devinfo *dinfo) { struct devlist *devlist_head; struct pci_map *pm, *next; int i; devlist_head = &pci_devq; if (dinfo->cfg.vpd.vpd_reg) { free(dinfo->cfg.vpd.vpd_ident, M_DEVBUF); for (i = 0; i < dinfo->cfg.vpd.vpd_rocnt; i++) free(dinfo->cfg.vpd.vpd_ros[i].value, M_DEVBUF); free(dinfo->cfg.vpd.vpd_ros, M_DEVBUF); for (i = 0; i < dinfo->cfg.vpd.vpd_wcnt; i++) free(dinfo->cfg.vpd.vpd_w[i].value, M_DEVBUF); free(dinfo->cfg.vpd.vpd_w, M_DEVBUF); } STAILQ_FOREACH_SAFE(pm, &dinfo->cfg.maps, pm_link, next) { free(pm, M_DEVBUF); } STAILQ_REMOVE(devlist_head, dinfo, pci_devinfo, pci_links); free(dinfo, M_DEVBUF); /* increment the generation count */ pci_generation++; /* we're losing one device */ pci_numdevs--; return (0); } /* * PCI power manangement */ int pci_set_powerstate_method(device_t dev, device_t child, int state) { struct pci_devinfo *dinfo = device_get_ivars(child); pcicfgregs *cfg = &dinfo->cfg; uint16_t status; int result, oldstate, highest, delay; if (cfg->pp.pp_cap == 0) return (EOPNOTSUPP); /* * Optimize a no state change request away. While it would be OK to * write to the hardware in theory, some devices have shown odd * behavior when going from D3 -> D3. */ oldstate = pci_get_powerstate(child); if (oldstate == state) return (0); /* * The PCI power management specification states that after a state * transition between PCI power states, system software must * guarantee a minimal delay before the function accesses the device. * Compute the worst case delay that we need to guarantee before we * access the device. Many devices will be responsive much more * quickly than this delay, but there are some that don't respond * instantly to state changes. Transitions to/from D3 state require * 10ms, while D2 requires 200us, and D0/1 require none. The delay * is done below with DELAY rather than a sleeper function because * this function can be called from contexts where we cannot sleep. */ highest = (oldstate > state) ? oldstate : state; if (highest == PCI_POWERSTATE_D3) delay = 10000; else if (highest == PCI_POWERSTATE_D2) delay = 200; else delay = 0; status = PCI_READ_CONFIG(dev, child, cfg->pp.pp_status, 2) & ~PCIM_PSTAT_DMASK; result = 0; switch (state) { case PCI_POWERSTATE_D0: status |= PCIM_PSTAT_D0; break; case PCI_POWERSTATE_D1: if ((cfg->pp.pp_cap & PCIM_PCAP_D1SUPP) == 0) return (EOPNOTSUPP); status |= PCIM_PSTAT_D1; break; case PCI_POWERSTATE_D2: if ((cfg->pp.pp_cap & PCIM_PCAP_D2SUPP) == 0) return (EOPNOTSUPP); status |= PCIM_PSTAT_D2; break; case PCI_POWERSTATE_D3: status |= PCIM_PSTAT_D3; break; default: return (EINVAL); } if (bootverbose) pci_printf(cfg, "Transition from D%d to D%d\n", oldstate, state); PCI_WRITE_CONFIG(dev, child, cfg->pp.pp_status, status, 2); if (delay) DELAY(delay); return (0); } int pci_get_powerstate_method(device_t dev, device_t child) { struct pci_devinfo *dinfo = device_get_ivars(child); pcicfgregs *cfg = &dinfo->cfg; uint16_t status; int result; if (cfg->pp.pp_cap != 0) { status = PCI_READ_CONFIG(dev, child, cfg->pp.pp_status, 2); switch (status & PCIM_PSTAT_DMASK) { case PCIM_PSTAT_D0: result = PCI_POWERSTATE_D0; break; case PCIM_PSTAT_D1: result = PCI_POWERSTATE_D1; break; case PCIM_PSTAT_D2: result = PCI_POWERSTATE_D2; break; case PCIM_PSTAT_D3: result = PCI_POWERSTATE_D3; break; default: result = PCI_POWERSTATE_UNKNOWN; break; } } else { /* No support, device is always at D0 */ result = PCI_POWERSTATE_D0; } return (result); } /* * Some convenience functions for PCI device drivers. */ static __inline void pci_set_command_bit(device_t dev, device_t child, uint16_t bit) { uint16_t command; command = PCI_READ_CONFIG(dev, child, PCIR_COMMAND, 2); command |= bit; PCI_WRITE_CONFIG(dev, child, PCIR_COMMAND, command, 2); } static __inline void pci_clear_command_bit(device_t dev, device_t child, uint16_t bit) { uint16_t command; command = PCI_READ_CONFIG(dev, child, PCIR_COMMAND, 2); command &= ~bit; PCI_WRITE_CONFIG(dev, child, PCIR_COMMAND, command, 2); } int pci_enable_busmaster_method(device_t dev, device_t child) { pci_set_command_bit(dev, child, PCIM_CMD_BUSMASTEREN); return (0); } int pci_disable_busmaster_method(device_t dev, device_t child) { pci_clear_command_bit(dev, child, PCIM_CMD_BUSMASTEREN); return (0); } int pci_enable_io_method(device_t dev, device_t child, int space) { uint16_t bit; switch(space) { case SYS_RES_IOPORT: bit = PCIM_CMD_PORTEN; break; case SYS_RES_MEMORY: bit = PCIM_CMD_MEMEN; break; default: return (EINVAL); } pci_set_command_bit(dev, child, bit); return (0); } int pci_disable_io_method(device_t dev, device_t child, int space) { uint16_t bit; switch(space) { case SYS_RES_IOPORT: bit = PCIM_CMD_PORTEN; break; case SYS_RES_MEMORY: bit = PCIM_CMD_MEMEN; break; default: return (EINVAL); } pci_clear_command_bit(dev, child, bit); return (0); } /* * New style pci driver. Parent device is either a pci-host-bridge or a * pci-pci-bridge. Both kinds are represented by instances of pcib. */ void pci_print_verbose(struct pci_devinfo *dinfo) { if (bootverbose) { pcicfgregs *cfg = &dinfo->cfg; printf("found->\tvendor=0x%04x, dev=0x%04x, revid=0x%02x\n", cfg->vendor, cfg->device, cfg->revid); printf("\tdomain=%d, bus=%d, slot=%d, func=%d\n", cfg->domain, cfg->bus, cfg->slot, cfg->func); printf("\tclass=%02x-%02x-%02x, hdrtype=0x%02x, mfdev=%d\n", cfg->baseclass, cfg->subclass, cfg->progif, cfg->hdrtype, cfg->mfdev); printf("\tcmdreg=0x%04x, statreg=0x%04x, cachelnsz=%d (dwords)\n", cfg->cmdreg, cfg->statreg, cfg->cachelnsz); printf("\tlattimer=0x%02x (%d ns), mingnt=0x%02x (%d ns), maxlat=0x%02x (%d ns)\n", cfg->lattimer, cfg->lattimer * 30, cfg->mingnt, cfg->mingnt * 250, cfg->maxlat, cfg->maxlat * 250); if (cfg->intpin > 0) printf("\tintpin=%c, irq=%d\n", cfg->intpin +'a' -1, cfg->intline); if (cfg->pp.pp_cap) { uint16_t status; status = pci_read_config(cfg->dev, cfg->pp.pp_status, 2); printf("\tpowerspec %d supports D0%s%s D3 current D%d\n", cfg->pp.pp_cap & PCIM_PCAP_SPEC, cfg->pp.pp_cap & PCIM_PCAP_D1SUPP ? " D1" : "", cfg->pp.pp_cap & PCIM_PCAP_D2SUPP ? " D2" : "", status & PCIM_PSTAT_DMASK); } if (cfg->msi.msi_location) { int ctrl; ctrl = cfg->msi.msi_ctrl; printf("\tMSI supports %d message%s%s%s\n", cfg->msi.msi_msgnum, (cfg->msi.msi_msgnum == 1) ? "" : "s", (ctrl & PCIM_MSICTRL_64BIT) ? ", 64 bit" : "", (ctrl & PCIM_MSICTRL_VECTOR) ? ", vector masks":""); } if (cfg->msix.msix_location) { printf("\tMSI-X supports %d message%s ", cfg->msix.msix_msgnum, (cfg->msix.msix_msgnum == 1) ? "" : "s"); if (cfg->msix.msix_table_bar == cfg->msix.msix_pba_bar) printf("in map 0x%x\n", cfg->msix.msix_table_bar); else printf("in maps 0x%x and 0x%x\n", cfg->msix.msix_table_bar, cfg->msix.msix_pba_bar); } } } static int pci_porten(device_t dev) { return (pci_read_config(dev, PCIR_COMMAND, 2) & PCIM_CMD_PORTEN) != 0; } static int pci_memen(device_t dev) { return (pci_read_config(dev, PCIR_COMMAND, 2) & PCIM_CMD_MEMEN) != 0; } static void pci_read_bar(device_t dev, int reg, pci_addr_t *mapp, pci_addr_t *testvalp) { struct pci_devinfo *dinfo; pci_addr_t map, testval; int ln2range; uint16_t cmd; /* * The device ROM BAR is special. It is always a 32-bit * memory BAR. Bit 0 is special and should not be set when * sizing the BAR. */ dinfo = device_get_ivars(dev); if (PCIR_IS_BIOS(&dinfo->cfg, reg)) { map = pci_read_config(dev, reg, 4); pci_write_config(dev, reg, 0xfffffffe, 4); testval = pci_read_config(dev, reg, 4); pci_write_config(dev, reg, map, 4); *mapp = map; *testvalp = testval; return; } map = pci_read_config(dev, reg, 4); ln2range = pci_maprange(map); if (ln2range == 64) map |= (pci_addr_t)pci_read_config(dev, reg + 4, 4) << 32; /* * Disable decoding via the command register before * determining the BAR's length since we will be placing it in * a weird state. */ cmd = pci_read_config(dev, PCIR_COMMAND, 2); pci_write_config(dev, PCIR_COMMAND, cmd & ~(PCI_BAR_MEM(map) ? PCIM_CMD_MEMEN : PCIM_CMD_PORTEN), 2); /* * Determine the BAR's length by writing all 1's. The bottom * log_2(size) bits of the BAR will stick as 0 when we read * the value back. */ pci_write_config(dev, reg, 0xffffffff, 4); testval = pci_read_config(dev, reg, 4); if (ln2range == 64) { pci_write_config(dev, reg + 4, 0xffffffff, 4); testval |= (pci_addr_t)pci_read_config(dev, reg + 4, 4) << 32; } /* * Restore the original value of the BAR. We may have reprogrammed * the BAR of the low-level console device and when booting verbose, * we need the console device addressable. */ pci_write_config(dev, reg, map, 4); if (ln2range == 64) pci_write_config(dev, reg + 4, map >> 32, 4); pci_write_config(dev, PCIR_COMMAND, cmd, 2); *mapp = map; *testvalp = testval; } static void pci_write_bar(device_t dev, struct pci_map *pm, pci_addr_t base) { struct pci_devinfo *dinfo; int ln2range; /* The device ROM BAR is always a 32-bit memory BAR. */ dinfo = device_get_ivars(dev); if (PCIR_IS_BIOS(&dinfo->cfg, pm->pm_reg)) ln2range = 32; else ln2range = pci_maprange(pm->pm_value); pci_write_config(dev, pm->pm_reg, base, 4); if (ln2range == 64) pci_write_config(dev, pm->pm_reg + 4, base >> 32, 4); pm->pm_value = pci_read_config(dev, pm->pm_reg, 4); if (ln2range == 64) pm->pm_value |= (pci_addr_t)pci_read_config(dev, pm->pm_reg + 4, 4) << 32; } struct pci_map * pci_find_bar(device_t dev, int reg) { struct pci_devinfo *dinfo; struct pci_map *pm; dinfo = device_get_ivars(dev); STAILQ_FOREACH(pm, &dinfo->cfg.maps, pm_link) { if (pm->pm_reg == reg) return (pm); } return (NULL); } int pci_bar_enabled(device_t dev, struct pci_map *pm) { struct pci_devinfo *dinfo; uint16_t cmd; dinfo = device_get_ivars(dev); if (PCIR_IS_BIOS(&dinfo->cfg, pm->pm_reg) && !(pm->pm_value & PCIM_BIOS_ENABLE)) return (0); cmd = pci_read_config(dev, PCIR_COMMAND, 2); if (PCIR_IS_BIOS(&dinfo->cfg, pm->pm_reg) || PCI_BAR_MEM(pm->pm_value)) return ((cmd & PCIM_CMD_MEMEN) != 0); else return ((cmd & PCIM_CMD_PORTEN) != 0); } static struct pci_map * pci_add_bar(device_t dev, int reg, pci_addr_t value, pci_addr_t size) { struct pci_devinfo *dinfo; struct pci_map *pm, *prev; dinfo = device_get_ivars(dev); pm = malloc(sizeof(*pm), M_DEVBUF, M_WAITOK | M_ZERO); pm->pm_reg = reg; pm->pm_value = value; pm->pm_size = size; STAILQ_FOREACH(prev, &dinfo->cfg.maps, pm_link) { KASSERT(prev->pm_reg != pm->pm_reg, ("duplicate map %02x", reg)); if (STAILQ_NEXT(prev, pm_link) == NULL || STAILQ_NEXT(prev, pm_link)->pm_reg > pm->pm_reg) break; } if (prev != NULL) STAILQ_INSERT_AFTER(&dinfo->cfg.maps, prev, pm, pm_link); else STAILQ_INSERT_TAIL(&dinfo->cfg.maps, pm, pm_link); return (pm); } static void pci_restore_bars(device_t dev) { struct pci_devinfo *dinfo; struct pci_map *pm; int ln2range; dinfo = device_get_ivars(dev); STAILQ_FOREACH(pm, &dinfo->cfg.maps, pm_link) { if (PCIR_IS_BIOS(&dinfo->cfg, pm->pm_reg)) ln2range = 32; else ln2range = pci_maprange(pm->pm_value); pci_write_config(dev, pm->pm_reg, pm->pm_value, 4); if (ln2range == 64) pci_write_config(dev, pm->pm_reg + 4, pm->pm_value >> 32, 4); } } /* * Add a resource based on a pci map register. Return 1 if the map * register is a 32bit map register or 2 if it is a 64bit register. */ static int pci_add_map(device_t bus, device_t dev, int reg, struct resource_list *rl, int force, int prefetch) { struct pci_map *pm; pci_addr_t base, map, testval; pci_addr_t start, end, count; int barlen, basezero, flags, maprange, mapsize, type; uint16_t cmd; struct resource *res; /* * The BAR may already exist if the device is a CardBus card * whose CIS is stored in this BAR. */ pm = pci_find_bar(dev, reg); if (pm != NULL) { maprange = pci_maprange(pm->pm_value); barlen = maprange == 64 ? 2 : 1; return (barlen); } pci_read_bar(dev, reg, &map, &testval); if (PCI_BAR_MEM(map)) { type = SYS_RES_MEMORY; if (map & PCIM_BAR_MEM_PREFETCH) prefetch = 1; } else type = SYS_RES_IOPORT; mapsize = pci_mapsize(testval); base = pci_mapbase(map); #ifdef __PCI_BAR_ZERO_VALID basezero = 0; #else basezero = base == 0; #endif maprange = pci_maprange(map); barlen = maprange == 64 ? 2 : 1; /* * For I/O registers, if bottom bit is set, and the next bit up * isn't clear, we know we have a BAR that doesn't conform to the * spec, so ignore it. Also, sanity check the size of the data * areas to the type of memory involved. Memory must be at least * 16 bytes in size, while I/O ranges must be at least 4. */ if (PCI_BAR_IO(testval) && (testval & PCIM_BAR_IO_RESERVED) != 0) return (barlen); if ((type == SYS_RES_MEMORY && mapsize < 4) || (type == SYS_RES_IOPORT && mapsize < 2)) return (barlen); /* Save a record of this BAR. */ pm = pci_add_bar(dev, reg, map, mapsize); if (bootverbose) { printf("\tmap[%02x]: type %s, range %2d, base %#jx, size %2d", reg, pci_maptype(map), maprange, (uintmax_t)base, mapsize); if (type == SYS_RES_IOPORT && !pci_porten(dev)) printf(", port disabled\n"); else if (type == SYS_RES_MEMORY && !pci_memen(dev)) printf(", memory disabled\n"); else printf(", enabled\n"); } /* * If base is 0, then we have problems if this architecture does * not allow that. It is best to ignore such entries for the * moment. These will be allocated later if the driver specifically * requests them. However, some removable busses look better when * all resources are allocated, so allow '0' to be overriden. * * Similarly treat maps whose values is the same as the test value * read back. These maps have had all f's written to them by the * BIOS in an attempt to disable the resources. */ if (!force && (basezero || map == testval)) return (barlen); if ((u_long)base != base) { device_printf(bus, "pci%d:%d:%d:%d bar %#x too many address bits", pci_get_domain(dev), pci_get_bus(dev), pci_get_slot(dev), pci_get_function(dev), reg); return (barlen); } /* * This code theoretically does the right thing, but has * undesirable side effects in some cases where peripherals * respond oddly to having these bits enabled. Let the user * be able to turn them off (since pci_enable_io_modes is 1 by * default). */ if (pci_enable_io_modes) { /* Turn on resources that have been left off by a lazy BIOS */ if (type == SYS_RES_IOPORT && !pci_porten(dev)) { cmd = pci_read_config(dev, PCIR_COMMAND, 2); cmd |= PCIM_CMD_PORTEN; pci_write_config(dev, PCIR_COMMAND, cmd, 2); } if (type == SYS_RES_MEMORY && !pci_memen(dev)) { cmd = pci_read_config(dev, PCIR_COMMAND, 2); cmd |= PCIM_CMD_MEMEN; pci_write_config(dev, PCIR_COMMAND, cmd, 2); } } else { if (type == SYS_RES_IOPORT && !pci_porten(dev)) return (barlen); if (type == SYS_RES_MEMORY && !pci_memen(dev)) return (barlen); } count = (pci_addr_t)1 << mapsize; flags = RF_ALIGNMENT_LOG2(mapsize); if (prefetch) flags |= RF_PREFETCHABLE; if (basezero || base == pci_mapbase(testval) || pci_clear_bars) { start = 0; /* Let the parent decide. */ end = ~0ul; } else { start = base; end = base + count - 1; } resource_list_add(rl, type, reg, start, end, count); /* * Try to allocate the resource for this BAR from our parent * so that this resource range is already reserved. The * driver for this device will later inherit this resource in * pci_alloc_resource(). */ res = resource_list_reserve(rl, bus, dev, type, ®, start, end, count, flags); if (pci_do_realloc_bars && res == NULL && (start != 0 || end != ~0ul)) { /* * If the allocation fails, try to allocate a resource for * this BAR using any available range. The firmware felt * it was important enough to assign a resource, so don't * disable decoding if we can help it. */ resource_list_delete(rl, type, reg); resource_list_add(rl, type, reg, 0, ~0ul, count); res = resource_list_reserve(rl, bus, dev, type, ®, 0, ~0ul, count, flags); } if (res == NULL) { /* * If the allocation fails, delete the resource list entry * and disable decoding for this device. * * If the driver requests this resource in the future, * pci_reserve_map() will try to allocate a fresh * resource range. */ resource_list_delete(rl, type, reg); pci_disable_io(dev, type); if (bootverbose) device_printf(bus, "pci%d:%d:%d:%d bar %#x failed to allocate\n", pci_get_domain(dev), pci_get_bus(dev), pci_get_slot(dev), pci_get_function(dev), reg); } else { start = rman_get_start(res); pci_write_bar(dev, pm, start); } return (barlen); } /* * For ATA devices we need to decide early what addressing mode to use. * Legacy demands that the primary and secondary ATA ports sits on the * same addresses that old ISA hardware did. This dictates that we use * those addresses and ignore the BAR's if we cannot set PCI native * addressing mode. */ static void pci_ata_maps(device_t bus, device_t dev, struct resource_list *rl, int force, uint32_t prefetchmask) { struct resource *r; int rid, type, progif; #if 0 /* if this device supports PCI native addressing use it */ progif = pci_read_config(dev, PCIR_PROGIF, 1); if ((progif & 0x8a) == 0x8a) { if (pci_mapbase(pci_read_config(dev, PCIR_BAR(0), 4)) && pci_mapbase(pci_read_config(dev, PCIR_BAR(2), 4))) { printf("Trying ATA native PCI addressing mode\n"); pci_write_config(dev, PCIR_PROGIF, progif | 0x05, 1); } } #endif progif = pci_read_config(dev, PCIR_PROGIF, 1); type = SYS_RES_IOPORT; if (progif & PCIP_STORAGE_IDE_MODEPRIM) { pci_add_map(bus, dev, PCIR_BAR(0), rl, force, prefetchmask & (1 << 0)); pci_add_map(bus, dev, PCIR_BAR(1), rl, force, prefetchmask & (1 << 1)); } else { rid = PCIR_BAR(0); resource_list_add(rl, type, rid, 0x1f0, 0x1f7, 8); r = resource_list_reserve(rl, bus, dev, type, &rid, 0x1f0, 0x1f7, 8, 0); rid = PCIR_BAR(1); resource_list_add(rl, type, rid, 0x3f6, 0x3f6, 1); r = resource_list_reserve(rl, bus, dev, type, &rid, 0x3f6, 0x3f6, 1, 0); } if (progif & PCIP_STORAGE_IDE_MODESEC) { pci_add_map(bus, dev, PCIR_BAR(2), rl, force, prefetchmask & (1 << 2)); pci_add_map(bus, dev, PCIR_BAR(3), rl, force, prefetchmask & (1 << 3)); } else { rid = PCIR_BAR(2); resource_list_add(rl, type, rid, 0x170, 0x177, 8); r = resource_list_reserve(rl, bus, dev, type, &rid, 0x170, 0x177, 8, 0); rid = PCIR_BAR(3); resource_list_add(rl, type, rid, 0x376, 0x376, 1); r = resource_list_reserve(rl, bus, dev, type, &rid, 0x376, 0x376, 1, 0); } pci_add_map(bus, dev, PCIR_BAR(4), rl, force, prefetchmask & (1 << 4)); pci_add_map(bus, dev, PCIR_BAR(5), rl, force, prefetchmask & (1 << 5)); } static void pci_assign_interrupt(device_t bus, device_t dev, int force_route) { struct pci_devinfo *dinfo = device_get_ivars(dev); pcicfgregs *cfg = &dinfo->cfg; char tunable_name[64]; int irq; /* Has to have an intpin to have an interrupt. */ if (cfg->intpin == 0) return; /* Let the user override the IRQ with a tunable. */ irq = PCI_INVALID_IRQ; snprintf(tunable_name, sizeof(tunable_name), "hw.pci%d.%d.%d.INT%c.irq", cfg->domain, cfg->bus, cfg->slot, cfg->intpin + 'A' - 1); if (TUNABLE_INT_FETCH(tunable_name, &irq) && (irq >= 255 || irq <= 0)) irq = PCI_INVALID_IRQ; /* * If we didn't get an IRQ via the tunable, then we either use the * IRQ value in the intline register or we ask the bus to route an * interrupt for us. If force_route is true, then we only use the * value in the intline register if the bus was unable to assign an * IRQ. */ if (!PCI_INTERRUPT_VALID(irq)) { if (!PCI_INTERRUPT_VALID(cfg->intline) || force_route) irq = PCI_ASSIGN_INTERRUPT(bus, dev); if (!PCI_INTERRUPT_VALID(irq)) irq = cfg->intline; } /* If after all that we don't have an IRQ, just bail. */ if (!PCI_INTERRUPT_VALID(irq)) return; /* Update the config register if it changed. */ if (irq != cfg->intline) { cfg->intline = irq; pci_write_config(dev, PCIR_INTLINE, irq, 1); } /* Add this IRQ as rid 0 interrupt resource. */ resource_list_add(&dinfo->resources, SYS_RES_IRQ, 0, irq, irq, 1); } /* Perform early OHCI takeover from SMM. */ static void ohci_early_takeover(device_t self) { struct resource *res; uint32_t ctl; int rid; int i; rid = PCIR_BAR(0); res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (res == NULL) return; ctl = bus_read_4(res, OHCI_CONTROL); if (ctl & OHCI_IR) { if (bootverbose) printf("ohci early: " "SMM active, request owner change\n"); bus_write_4(res, OHCI_COMMAND_STATUS, OHCI_OCR); for (i = 0; (i < 100) && (ctl & OHCI_IR); i++) { DELAY(1000); ctl = bus_read_4(res, OHCI_CONTROL); } if (ctl & OHCI_IR) { if (bootverbose) printf("ohci early: " "SMM does not respond, resetting\n"); bus_write_4(res, OHCI_CONTROL, OHCI_HCFS_RESET); } /* Disable interrupts */ bus_write_4(res, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS); } bus_release_resource(self, SYS_RES_MEMORY, rid, res); } /* Perform early UHCI takeover from SMM. */ static void uhci_early_takeover(device_t self) { struct resource *res; int rid; /* * Set the PIRQD enable bit and switch off all the others. We don't * want legacy support to interfere with us XXX Does this also mean * that the BIOS won't touch the keyboard anymore if it is connected * to the ports of the root hub? */ pci_write_config(self, PCI_LEGSUP, PCI_LEGSUP_USBPIRQDEN, 2); /* Disable interrupts */ rid = PCI_UHCI_BASE_REG; res = bus_alloc_resource_any(self, SYS_RES_IOPORT, &rid, RF_ACTIVE); if (res != NULL) { bus_write_2(res, UHCI_INTR, 0); bus_release_resource(self, SYS_RES_IOPORT, rid, res); } } /* Perform early EHCI takeover from SMM. */ static void ehci_early_takeover(device_t self) { struct resource *res; uint32_t cparams; uint32_t eec; uint8_t eecp; uint8_t bios_sem; uint8_t offs; int rid; int i; rid = PCIR_BAR(0); res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (res == NULL) return; cparams = bus_read_4(res, EHCI_HCCPARAMS); /* Synchronise with the BIOS if it owns the controller. */ for (eecp = EHCI_HCC_EECP(cparams); eecp != 0; eecp = EHCI_EECP_NEXT(eec)) { eec = pci_read_config(self, eecp, 4); if (EHCI_EECP_ID(eec) != EHCI_EC_LEGSUP) { continue; } bios_sem = pci_read_config(self, eecp + EHCI_LEGSUP_BIOS_SEM, 1); if (bios_sem == 0) { continue; } if (bootverbose) printf("ehci early: " "SMM active, request owner change\n"); pci_write_config(self, eecp + EHCI_LEGSUP_OS_SEM, 1, 1); for (i = 0; (i < 100) && (bios_sem != 0); i++) { DELAY(1000); bios_sem = pci_read_config(self, eecp + EHCI_LEGSUP_BIOS_SEM, 1); } if (bios_sem != 0) { if (bootverbose) printf("ehci early: " "SMM does not respond\n"); } /* Disable interrupts */ offs = EHCI_CAPLENGTH(bus_read_4(res, EHCI_CAPLEN_HCIVERSION)); bus_write_4(res, offs + EHCI_USBINTR, 0); } bus_release_resource(self, SYS_RES_MEMORY, rid, res); } /* Perform early XHCI takeover from SMM. */ static void xhci_early_takeover(device_t self) { struct resource *res; uint32_t cparams; uint32_t eec; uint8_t eecp; uint8_t bios_sem; uint8_t offs; int rid; int i; rid = PCIR_BAR(0); res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (res == NULL) return; cparams = bus_read_4(res, XHCI_HCSPARAMS0); eec = -1; /* Synchronise with the BIOS if it owns the controller. */ for (eecp = XHCI_HCS0_XECP(cparams) << 2; eecp != 0 && XHCI_XECP_NEXT(eec); eecp += XHCI_XECP_NEXT(eec) << 2) { eec = bus_read_4(res, eecp); if (XHCI_XECP_ID(eec) != XHCI_ID_USB_LEGACY) continue; bios_sem = bus_read_1(res, eecp + XHCI_XECP_BIOS_SEM); if (bios_sem == 0) continue; if (bootverbose) printf("xhci early: " "SMM active, request owner change\n"); bus_write_1(res, eecp + XHCI_XECP_OS_SEM, 1); /* wait a maximum of 5 second */ for (i = 0; (i < 5000) && (bios_sem != 0); i++) { DELAY(1000); bios_sem = bus_read_1(res, eecp + XHCI_XECP_BIOS_SEM); } if (bios_sem != 0) { if (bootverbose) printf("xhci early: " "SMM does not respond\n"); } /* Disable interrupts */ offs = bus_read_1(res, XHCI_CAPLENGTH); bus_write_4(res, offs + XHCI_USBCMD, 0); bus_read_4(res, offs + XHCI_USBSTS); } bus_release_resource(self, SYS_RES_MEMORY, rid, res); } #if defined(NEW_PCIB) && defined(PCI_RES_BUS) static void pci_reserve_secbus(device_t bus, device_t dev, pcicfgregs *cfg, struct resource_list *rl) { struct resource *res; char *cp; u_long start, end, count; int rid, sec_bus, sec_reg, sub_bus, sub_reg, sup_bus; switch (cfg->hdrtype & PCIM_HDRTYPE) { case PCIM_HDRTYPE_BRIDGE: sec_reg = PCIR_SECBUS_1; sub_reg = PCIR_SUBBUS_1; break; case PCIM_HDRTYPE_CARDBUS: sec_reg = PCIR_SECBUS_2; sub_reg = PCIR_SUBBUS_2; break; default: return; } /* * If the existing bus range is valid, attempt to reserve it * from our parent. If this fails for any reason, clear the * secbus and subbus registers. * * XXX: Should we reset sub_bus to sec_bus if it is < sec_bus? * This would at least preserve the existing sec_bus if it is * valid. */ sec_bus = PCI_READ_CONFIG(bus, dev, sec_reg, 1); sub_bus = PCI_READ_CONFIG(bus, dev, sub_reg, 1); /* Quirk handling. */ switch (pci_get_devid(dev)) { case 0x12258086: /* Intel 82454KX/GX (Orion) */ sup_bus = pci_read_config(dev, 0x41, 1); if (sup_bus != 0xff) { sec_bus = sup_bus + 1; sub_bus = sup_bus + 1; PCI_WRITE_CONFIG(bus, dev, sec_reg, sec_bus, 1); PCI_WRITE_CONFIG(bus, dev, sub_reg, sub_bus, 1); } break; case 0x00dd10de: /* Compaq R3000 BIOS sets wrong subordinate bus number. */ if ((cp = kern_getenv("smbios.planar.maker")) == NULL) break; if (strncmp(cp, "Compal", 6) != 0) { freeenv(cp); break; } freeenv(cp); if ((cp = kern_getenv("smbios.planar.product")) == NULL) break; if (strncmp(cp, "08A0", 4) != 0) { freeenv(cp); break; } freeenv(cp); if (sub_bus < 0xa) { sub_bus = 0xa; PCI_WRITE_CONFIG(bus, dev, sub_reg, sub_bus, 1); } break; } if (bootverbose) printf("\tsecbus=%d, subbus=%d\n", sec_bus, sub_bus); if (sec_bus > 0 && sub_bus >= sec_bus) { start = sec_bus; end = sub_bus; count = end - start + 1; resource_list_add(rl, PCI_RES_BUS, 0, 0ul, ~0ul, count); /* * If requested, clear secondary bus registers in * bridge devices to force a complete renumbering * rather than reserving the existing range. However, * preserve the existing size. */ if (pci_clear_buses) goto clear; rid = 0; res = resource_list_reserve(rl, bus, dev, PCI_RES_BUS, &rid, start, end, count, 0); if (res != NULL) return; if (bootverbose) device_printf(bus, "pci%d:%d:%d:%d secbus failed to allocate\n", pci_get_domain(dev), pci_get_bus(dev), pci_get_slot(dev), pci_get_function(dev)); } clear: PCI_WRITE_CONFIG(bus, dev, sec_reg, 0, 1); PCI_WRITE_CONFIG(bus, dev, sub_reg, 0, 1); } static struct resource * pci_alloc_secbus(device_t dev, device_t child, int *rid, u_long start, u_long end, u_long count, u_int flags) { struct pci_devinfo *dinfo; pcicfgregs *cfg; struct resource_list *rl; struct resource *res; int sec_reg, sub_reg; dinfo = device_get_ivars(child); cfg = &dinfo->cfg; rl = &dinfo->resources; switch (cfg->hdrtype & PCIM_HDRTYPE) { case PCIM_HDRTYPE_BRIDGE: sec_reg = PCIR_SECBUS_1; sub_reg = PCIR_SUBBUS_1; break; case PCIM_HDRTYPE_CARDBUS: sec_reg = PCIR_SECBUS_2; sub_reg = PCIR_SUBBUS_2; break; default: return (NULL); } if (*rid != 0) return (NULL); if (resource_list_find(rl, PCI_RES_BUS, *rid) == NULL) resource_list_add(rl, PCI_RES_BUS, *rid, start, end, count); if (!resource_list_reserved(rl, PCI_RES_BUS, *rid)) { res = resource_list_reserve(rl, dev, child, PCI_RES_BUS, rid, start, end, count, flags & ~RF_ACTIVE); if (res == NULL) { resource_list_delete(rl, PCI_RES_BUS, *rid); device_printf(child, "allocating %lu bus%s failed\n", count, count == 1 ? "" : "es"); return (NULL); } if (bootverbose) device_printf(child, "Lazy allocation of %lu bus%s at %lu\n", count, count == 1 ? "" : "es", rman_get_start(res)); PCI_WRITE_CONFIG(dev, child, sec_reg, rman_get_start(res), 1); PCI_WRITE_CONFIG(dev, child, sub_reg, rman_get_end(res), 1); } return (resource_list_alloc(rl, dev, child, PCI_RES_BUS, rid, start, end, count, flags)); } #endif void pci_add_resources(device_t bus, device_t dev, int force, uint32_t prefetchmask) { struct pci_devinfo *dinfo; pcicfgregs *cfg; struct resource_list *rl; const struct pci_quirk *q; uint32_t devid; int i; dinfo = device_get_ivars(dev); cfg = &dinfo->cfg; rl = &dinfo->resources; devid = (cfg->device << 16) | cfg->vendor; /* ATA devices needs special map treatment */ if ((pci_get_class(dev) == PCIC_STORAGE) && (pci_get_subclass(dev) == PCIS_STORAGE_IDE) && ((pci_get_progif(dev) & PCIP_STORAGE_IDE_MASTERDEV) || (!pci_read_config(dev, PCIR_BAR(0), 4) && !pci_read_config(dev, PCIR_BAR(2), 4))) ) pci_ata_maps(bus, dev, rl, force, prefetchmask); else for (i = 0; i < cfg->nummaps;) { /* * Skip quirked resources. */ for (q = &pci_quirks[0]; q->devid != 0; q++) if (q->devid == devid && q->type == PCI_QUIRK_UNMAP_REG && q->arg1 == PCIR_BAR(i)) break; if (q->devid != 0) { i++; continue; } i += pci_add_map(bus, dev, PCIR_BAR(i), rl, force, prefetchmask & (1 << i)); } /* * Add additional, quirked resources. */ for (q = &pci_quirks[0]; q->devid != 0; q++) if (q->devid == devid && q->type == PCI_QUIRK_MAP_REG) pci_add_map(bus, dev, q->arg1, rl, force, 0); if (cfg->intpin > 0 && PCI_INTERRUPT_VALID(cfg->intline)) { #ifdef __PCI_REROUTE_INTERRUPT /* * Try to re-route interrupts. Sometimes the BIOS or * firmware may leave bogus values in these registers. * If the re-route fails, then just stick with what we * have. */ pci_assign_interrupt(bus, dev, 1); #else pci_assign_interrupt(bus, dev, 0); #endif } if (pci_usb_takeover && pci_get_class(dev) == PCIC_SERIALBUS && pci_get_subclass(dev) == PCIS_SERIALBUS_USB) { if (pci_get_progif(dev) == PCIP_SERIALBUS_USB_XHCI) xhci_early_takeover(dev); else if (pci_get_progif(dev) == PCIP_SERIALBUS_USB_EHCI) ehci_early_takeover(dev); else if (pci_get_progif(dev) == PCIP_SERIALBUS_USB_OHCI) ohci_early_takeover(dev); else if (pci_get_progif(dev) == PCIP_SERIALBUS_USB_UHCI) uhci_early_takeover(dev); } #if defined(NEW_PCIB) && defined(PCI_RES_BUS) /* * Reserve resources for secondary bus ranges behind bridge * devices. */ pci_reserve_secbus(bus, dev, cfg, rl); #endif } static struct pci_devinfo * pci_identify_function(device_t pcib, device_t dev, int domain, int busno, int slot, int func, size_t dinfo_size) { struct pci_devinfo *dinfo; dinfo = pci_read_device(pcib, domain, busno, slot, func, dinfo_size); if (dinfo != NULL) pci_add_child(dev, dinfo); return (dinfo); } void pci_add_children(device_t dev, int domain, int busno, size_t dinfo_size) { #define REG(n, w) PCIB_READ_CONFIG(pcib, busno, s, f, n, w) device_t pcib = device_get_parent(dev); struct pci_devinfo *dinfo; int maxslots; int s, f, pcifunchigh; uint8_t hdrtype; int first_func; /* * Try to detect a device at slot 0, function 0. If it exists, try to * enable ARI. We must enable ARI before detecting the rest of the * functions on this bus as ARI changes the set of slots and functions * that are legal on this bus. */ dinfo = pci_identify_function(pcib, dev, domain, busno, 0, 0, dinfo_size); if (dinfo != NULL && pci_enable_ari) PCIB_TRY_ENABLE_ARI(pcib, dinfo->cfg.dev); /* * Start looking for new devices on slot 0 at function 1 because we * just identified the device at slot 0, function 0. */ first_func = 1; KASSERT(dinfo_size >= sizeof(struct pci_devinfo), ("dinfo_size too small")); maxslots = PCIB_MAXSLOTS(pcib); for (s = 0; s <= maxslots; s++, first_func = 0) { pcifunchigh = 0; f = 0; DELAY(1); hdrtype = REG(PCIR_HDRTYPE, 1); if ((hdrtype & PCIM_HDRTYPE) > PCI_MAXHDRTYPE) continue; if (hdrtype & PCIM_MFDEV) pcifunchigh = PCIB_MAXFUNCS(pcib); for (f = first_func; f <= pcifunchigh; f++) pci_identify_function(pcib, dev, domain, busno, s, f, dinfo_size); } #undef REG } void pci_add_child(device_t bus, struct pci_devinfo *dinfo) { dinfo->cfg.dev = device_add_child(bus, NULL, -1); device_set_ivars(dinfo->cfg.dev, dinfo); resource_list_init(&dinfo->resources); pci_cfg_save(dinfo->cfg.dev, dinfo, 0); pci_cfg_restore(dinfo->cfg.dev, dinfo); pci_print_verbose(dinfo); pci_add_resources(bus, dinfo->cfg.dev, 0, 0); pci_child_added(dinfo->cfg.dev); } void pci_child_added_method(device_t dev, device_t child) { } static int pci_probe(device_t dev) { device_set_desc(dev, "PCI bus"); /* Allow other subclasses to override this driver. */ return (BUS_PROBE_GENERIC); } int pci_attach_common(device_t dev) { struct pci_softc *sc; int busno, domain; #ifdef PCI_DMA_BOUNDARY int error, tag_valid; #endif #ifdef PCI_RES_BUS int rid; #endif sc = device_get_softc(dev); domain = pcib_get_domain(dev); busno = pcib_get_bus(dev); #ifdef PCI_RES_BUS rid = 0; sc->sc_bus = bus_alloc_resource(dev, PCI_RES_BUS, &rid, busno, busno, 1, 0); if (sc->sc_bus == NULL) { device_printf(dev, "failed to allocate bus number\n"); return (ENXIO); } #endif if (bootverbose) device_printf(dev, "domain=%d, physical bus=%d\n", domain, busno); #ifdef PCI_DMA_BOUNDARY tag_valid = 0; if (device_get_devclass(device_get_parent(device_get_parent(dev))) != devclass_find("pci")) { error = bus_dma_tag_create(bus_get_dma_tag(dev), 1, PCI_DMA_BOUNDARY, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, BUS_SPACE_MAXSIZE, BUS_SPACE_UNRESTRICTED, BUS_SPACE_MAXSIZE, 0, NULL, NULL, &sc->sc_dma_tag); if (error) device_printf(dev, "Failed to create DMA tag: %d\n", error); else tag_valid = 1; } if (!tag_valid) #endif sc->sc_dma_tag = bus_get_dma_tag(dev); return (0); } static int pci_attach(device_t dev) { int busno, domain, error; error = pci_attach_common(dev); if (error) return (error); /* * Since there can be multiple independantly numbered PCI * busses on systems with multiple PCI domains, we can't use * the unit number to decide which bus we are probing. We ask * the parent pcib what our domain and bus numbers are. */ domain = pcib_get_domain(dev); busno = pcib_get_bus(dev); pci_add_children(dev, domain, busno, sizeof(struct pci_devinfo)); return (bus_generic_attach(dev)); } #ifdef PCI_RES_BUS static int pci_detach(device_t dev) { struct pci_softc *sc; int error; error = bus_generic_detach(dev); if (error) return (error); sc = device_get_softc(dev); return (bus_release_resource(dev, PCI_RES_BUS, 0, sc->sc_bus)); } #endif static void pci_set_power_child(device_t dev, device_t child, int state) { struct pci_devinfo *dinfo; + device_t pcib; int dstate; /* * Set the device to the given state. If the firmware suggests * a different power state, use it instead. If power management * is not present, the firmware is responsible for managing * device power. Skip children who aren't attached since they * are handled separately. */ + pcib = device_get_parent(dev); dinfo = device_get_ivars(child); dstate = state; if (device_is_attached(child) && - PCIB_POWER_FOR_SLEEP(dev, child, &dstate) == 0) + PCIB_POWER_FOR_SLEEP(pcib, child, &dstate) == 0) pci_set_powerstate(child, dstate); } int pci_suspend_child(device_t dev, device_t child) { struct pci_devinfo *dinfo; int error; dinfo = device_get_ivars(child); /* * Save the PCI configuration space for the child and set the * device in the appropriate power state for this sleep state. */ pci_cfg_save(child, dinfo, 0); /* Suspend devices before potentially powering them down. */ error = bus_generic_suspend_child(dev, child); if (error) return (error); if (pci_do_power_suspend) pci_set_power_child(dev, child, PCI_POWERSTATE_D3); return (0); } int pci_resume_child(device_t dev, device_t child) { struct pci_devinfo *dinfo; if (pci_do_power_resume) pci_set_power_child(dev, child, PCI_POWERSTATE_D0); dinfo = device_get_ivars(child); pci_cfg_restore(child, dinfo); if (!device_is_attached(child)) pci_cfg_save(child, dinfo, 1); bus_generic_resume_child(dev, child); return (0); } int pci_resume(device_t dev) { device_t child, *devlist; int error, i, numdevs; if ((error = device_get_children(dev, &devlist, &numdevs)) != 0) return (error); /* * Resume critical devices first, then everything else later. */ for (i = 0; i < numdevs; i++) { child = devlist[i]; switch (pci_get_class(child)) { case PCIC_DISPLAY: case PCIC_MEMORY: case PCIC_BRIDGE: case PCIC_BASEPERIPH: BUS_RESUME_CHILD(dev, child); break; } } for (i = 0; i < numdevs; i++) { child = devlist[i]; switch (pci_get_class(child)) { case PCIC_DISPLAY: case PCIC_MEMORY: case PCIC_BRIDGE: case PCIC_BASEPERIPH: break; default: BUS_RESUME_CHILD(dev, child); } } free(devlist, M_TEMP); return (0); } static void pci_load_vendor_data(void) { caddr_t data; void *ptr; size_t sz; data = preload_search_by_type("pci_vendor_data"); if (data != NULL) { ptr = preload_fetch_addr(data); sz = preload_fetch_size(data); if (ptr != NULL && sz != 0) { pci_vendordata = ptr; pci_vendordata_size = sz; /* terminate the database */ pci_vendordata[pci_vendordata_size] = '\n'; } } } void pci_driver_added(device_t dev, driver_t *driver) { int numdevs; device_t *devlist; device_t child; struct pci_devinfo *dinfo; int i; if (bootverbose) device_printf(dev, "driver added\n"); DEVICE_IDENTIFY(driver, dev); if (device_get_children(dev, &devlist, &numdevs) != 0) return; for (i = 0; i < numdevs; i++) { child = devlist[i]; if (device_get_state(child) != DS_NOTPRESENT) continue; dinfo = device_get_ivars(child); pci_print_verbose(dinfo); if (bootverbose) pci_printf(&dinfo->cfg, "reprobing on driver added\n"); pci_cfg_restore(child, dinfo); if (device_probe_and_attach(child) != 0) pci_child_detached(dev, child); } free(devlist, M_TEMP); } int pci_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, driver_filter_t *filter, driver_intr_t *intr, void *arg, void **cookiep) { struct pci_devinfo *dinfo; struct msix_table_entry *mte; struct msix_vector *mv; uint64_t addr; uint32_t data; void *cookie; int error, rid; error = bus_generic_setup_intr(dev, child, irq, flags, filter, intr, arg, &cookie); if (error) return (error); /* If this is not a direct child, just bail out. */ if (device_get_parent(child) != dev) { *cookiep = cookie; return(0); } rid = rman_get_rid(irq); if (rid == 0) { /* Make sure that INTx is enabled */ pci_clear_command_bit(dev, child, PCIM_CMD_INTxDIS); } else { /* * Check to see if the interrupt is MSI or MSI-X. * Ask our parent to map the MSI and give * us the address and data register values. * If we fail for some reason, teardown the * interrupt handler. */ dinfo = device_get_ivars(child); if (dinfo->cfg.msi.msi_alloc > 0) { if (dinfo->cfg.msi.msi_addr == 0) { KASSERT(dinfo->cfg.msi.msi_handlers == 0, ("MSI has handlers, but vectors not mapped")); error = PCIB_MAP_MSI(device_get_parent(dev), child, rman_get_start(irq), &addr, &data); if (error) goto bad; dinfo->cfg.msi.msi_addr = addr; dinfo->cfg.msi.msi_data = data; } if (dinfo->cfg.msi.msi_handlers == 0) pci_enable_msi(child, dinfo->cfg.msi.msi_addr, dinfo->cfg.msi.msi_data); dinfo->cfg.msi.msi_handlers++; } else { KASSERT(dinfo->cfg.msix.msix_alloc > 0, ("No MSI or MSI-X interrupts allocated")); KASSERT(rid <= dinfo->cfg.msix.msix_table_len, ("MSI-X index too high")); mte = &dinfo->cfg.msix.msix_table[rid - 1]; KASSERT(mte->mte_vector != 0, ("no message vector")); mv = &dinfo->cfg.msix.msix_vectors[mte->mte_vector - 1]; KASSERT(mv->mv_irq == rman_get_start(irq), ("IRQ mismatch")); if (mv->mv_address == 0) { KASSERT(mte->mte_handlers == 0, ("MSI-X table entry has handlers, but vector not mapped")); error = PCIB_MAP_MSI(device_get_parent(dev), child, rman_get_start(irq), &addr, &data); if (error) goto bad; mv->mv_address = addr; mv->mv_data = data; } if (mte->mte_handlers == 0) { pci_enable_msix(child, rid - 1, mv->mv_address, mv->mv_data); pci_unmask_msix(child, rid - 1); } mte->mte_handlers++; } if (!pci_has_quirk(pci_get_devid(dev), PCI_QUIRK_MSI_INTX_BUG)) { /* * Make sure that INTx is disabled if we are * using MSI/MSIX */ pci_set_command_bit(dev, child, PCIM_CMD_INTxDIS); } bad: if (error) { (void)bus_generic_teardown_intr(dev, child, irq, cookie); return (error); } } *cookiep = cookie; return (0); } int pci_teardown_intr(device_t dev, device_t child, struct resource *irq, void *cookie) { struct msix_table_entry *mte; struct resource_list_entry *rle; struct pci_devinfo *dinfo; int error, rid; if (irq == NULL || !(rman_get_flags(irq) & RF_ACTIVE)) return (EINVAL); /* If this isn't a direct child, just bail out */ if (device_get_parent(child) != dev) return(bus_generic_teardown_intr(dev, child, irq, cookie)); rid = rman_get_rid(irq); if (rid == 0) { /* Mask INTx */ pci_set_command_bit(dev, child, PCIM_CMD_INTxDIS); } else { /* * Check to see if the interrupt is MSI or MSI-X. If so, * decrement the appropriate handlers count and mask the * MSI-X message, or disable MSI messages if the count * drops to 0. */ dinfo = device_get_ivars(child); rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, rid); if (rle->res != irq) return (EINVAL); if (dinfo->cfg.msi.msi_alloc > 0) { KASSERT(rid <= dinfo->cfg.msi.msi_alloc, ("MSI-X index too high")); if (dinfo->cfg.msi.msi_handlers == 0) return (EINVAL); dinfo->cfg.msi.msi_handlers--; if (dinfo->cfg.msi.msi_handlers == 0) pci_disable_msi(child); } else { KASSERT(dinfo->cfg.msix.msix_alloc > 0, ("No MSI or MSI-X interrupts allocated")); KASSERT(rid <= dinfo->cfg.msix.msix_table_len, ("MSI-X index too high")); mte = &dinfo->cfg.msix.msix_table[rid - 1]; if (mte->mte_handlers == 0) return (EINVAL); mte->mte_handlers--; if (mte->mte_handlers == 0) pci_mask_msix(child, rid - 1); } } error = bus_generic_teardown_intr(dev, child, irq, cookie); if (rid > 0) KASSERT(error == 0, ("%s: generic teardown failed for MSI/MSI-X", __func__)); return (error); } int pci_print_child(device_t dev, device_t child) { struct pci_devinfo *dinfo; struct resource_list *rl; int retval = 0; dinfo = device_get_ivars(child); rl = &dinfo->resources; retval += bus_print_child_header(dev, child); retval += resource_list_print_type(rl, "port", SYS_RES_IOPORT, "%#lx"); retval += resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#lx"); retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%ld"); if (device_get_flags(dev)) retval += printf(" flags %#x", device_get_flags(dev)); retval += printf(" at device %d.%d", pci_get_slot(child), pci_get_function(child)); retval += bus_print_child_domain(dev, child); retval += bus_print_child_footer(dev, child); return (retval); } static const struct { int class; int subclass; int report; /* 0 = bootverbose, 1 = always */ const char *desc; } pci_nomatch_tab[] = { {PCIC_OLD, -1, 1, "old"}, {PCIC_OLD, PCIS_OLD_NONVGA, 1, "non-VGA display device"}, {PCIC_OLD, PCIS_OLD_VGA, 1, "VGA-compatible display device"}, {PCIC_STORAGE, -1, 1, "mass storage"}, {PCIC_STORAGE, PCIS_STORAGE_SCSI, 1, "SCSI"}, {PCIC_STORAGE, PCIS_STORAGE_IDE, 1, "ATA"}, {PCIC_STORAGE, PCIS_STORAGE_FLOPPY, 1, "floppy disk"}, {PCIC_STORAGE, PCIS_STORAGE_IPI, 1, "IPI"}, {PCIC_STORAGE, PCIS_STORAGE_RAID, 1, "RAID"}, {PCIC_STORAGE, PCIS_STORAGE_ATA_ADMA, 1, "ATA (ADMA)"}, {PCIC_STORAGE, PCIS_STORAGE_SATA, 1, "SATA"}, {PCIC_STORAGE, PCIS_STORAGE_SAS, 1, "SAS"}, {PCIC_STORAGE, PCIS_STORAGE_NVM, 1, "NVM"}, {PCIC_NETWORK, -1, 1, "network"}, {PCIC_NETWORK, PCIS_NETWORK_ETHERNET, 1, "ethernet"}, {PCIC_NETWORK, PCIS_NETWORK_TOKENRING, 1, "token ring"}, {PCIC_NETWORK, PCIS_NETWORK_FDDI, 1, "fddi"}, {PCIC_NETWORK, PCIS_NETWORK_ATM, 1, "ATM"}, {PCIC_NETWORK, PCIS_NETWORK_ISDN, 1, "ISDN"}, {PCIC_DISPLAY, -1, 1, "display"}, {PCIC_DISPLAY, PCIS_DISPLAY_VGA, 1, "VGA"}, {PCIC_DISPLAY, PCIS_DISPLAY_XGA, 1, "XGA"}, {PCIC_DISPLAY, PCIS_DISPLAY_3D, 1, "3D"}, {PCIC_MULTIMEDIA, -1, 1, "multimedia"}, {PCIC_MULTIMEDIA, PCIS_MULTIMEDIA_VIDEO, 1, "video"}, {PCIC_MULTIMEDIA, PCIS_MULTIMEDIA_AUDIO, 1, "audio"}, {PCIC_MULTIMEDIA, PCIS_MULTIMEDIA_TELE, 1, "telephony"}, {PCIC_MULTIMEDIA, PCIS_MULTIMEDIA_HDA, 1, "HDA"}, {PCIC_MEMORY, -1, 1, "memory"}, {PCIC_MEMORY, PCIS_MEMORY_RAM, 1, "RAM"}, {PCIC_MEMORY, PCIS_MEMORY_FLASH, 1, "flash"}, {PCIC_BRIDGE, -1, 1, "bridge"}, {PCIC_BRIDGE, PCIS_BRIDGE_HOST, 1, "HOST-PCI"}, {PCIC_BRIDGE, PCIS_BRIDGE_ISA, 1, "PCI-ISA"}, {PCIC_BRIDGE, PCIS_BRIDGE_EISA, 1, "PCI-EISA"}, {PCIC_BRIDGE, PCIS_BRIDGE_MCA, 1, "PCI-MCA"}, {PCIC_BRIDGE, PCIS_BRIDGE_PCI, 1, "PCI-PCI"}, {PCIC_BRIDGE, PCIS_BRIDGE_PCMCIA, 1, "PCI-PCMCIA"}, {PCIC_BRIDGE, PCIS_BRIDGE_NUBUS, 1, "PCI-NuBus"}, {PCIC_BRIDGE, PCIS_BRIDGE_CARDBUS, 1, "PCI-CardBus"}, {PCIC_BRIDGE, PCIS_BRIDGE_RACEWAY, 1, "PCI-RACEway"}, {PCIC_SIMPLECOMM, -1, 1, "simple comms"}, {PCIC_SIMPLECOMM, PCIS_SIMPLECOMM_UART, 1, "UART"}, /* could detect 16550 */ {PCIC_SIMPLECOMM, PCIS_SIMPLECOMM_PAR, 1, "parallel port"}, {PCIC_SIMPLECOMM, PCIS_SIMPLECOMM_MULSER, 1, "multiport serial"}, {PCIC_SIMPLECOMM, PCIS_SIMPLECOMM_MODEM, 1, "generic modem"}, {PCIC_BASEPERIPH, -1, 0, "base peripheral"}, {PCIC_BASEPERIPH, PCIS_BASEPERIPH_PIC, 1, "interrupt controller"}, {PCIC_BASEPERIPH, PCIS_BASEPERIPH_DMA, 1, "DMA controller"}, {PCIC_BASEPERIPH, PCIS_BASEPERIPH_TIMER, 1, "timer"}, {PCIC_BASEPERIPH, PCIS_BASEPERIPH_RTC, 1, "realtime clock"}, {PCIC_BASEPERIPH, PCIS_BASEPERIPH_PCIHOT, 1, "PCI hot-plug controller"}, {PCIC_BASEPERIPH, PCIS_BASEPERIPH_SDHC, 1, "SD host controller"}, {PCIC_BASEPERIPH, PCIS_BASEPERIPH_IOMMU, 1, "IOMMU"}, {PCIC_INPUTDEV, -1, 1, "input device"}, {PCIC_INPUTDEV, PCIS_INPUTDEV_KEYBOARD, 1, "keyboard"}, {PCIC_INPUTDEV, PCIS_INPUTDEV_DIGITIZER,1, "digitizer"}, {PCIC_INPUTDEV, PCIS_INPUTDEV_MOUSE, 1, "mouse"}, {PCIC_INPUTDEV, PCIS_INPUTDEV_SCANNER, 1, "scanner"}, {PCIC_INPUTDEV, PCIS_INPUTDEV_GAMEPORT, 1, "gameport"}, {PCIC_DOCKING, -1, 1, "docking station"}, {PCIC_PROCESSOR, -1, 1, "processor"}, {PCIC_SERIALBUS, -1, 1, "serial bus"}, {PCIC_SERIALBUS, PCIS_SERIALBUS_FW, 1, "FireWire"}, {PCIC_SERIALBUS, PCIS_SERIALBUS_ACCESS, 1, "AccessBus"}, {PCIC_SERIALBUS, PCIS_SERIALBUS_SSA, 1, "SSA"}, {PCIC_SERIALBUS, PCIS_SERIALBUS_USB, 1, "USB"}, {PCIC_SERIALBUS, PCIS_SERIALBUS_FC, 1, "Fibre Channel"}, {PCIC_SERIALBUS, PCIS_SERIALBUS_SMBUS, 0, "SMBus"}, {PCIC_WIRELESS, -1, 1, "wireless controller"}, {PCIC_WIRELESS, PCIS_WIRELESS_IRDA, 1, "iRDA"}, {PCIC_WIRELESS, PCIS_WIRELESS_IR, 1, "IR"}, {PCIC_WIRELESS, PCIS_WIRELESS_RF, 1, "RF"}, {PCIC_INTELLIIO, -1, 1, "intelligent I/O controller"}, {PCIC_INTELLIIO, PCIS_INTELLIIO_I2O, 1, "I2O"}, {PCIC_SATCOM, -1, 1, "satellite communication"}, {PCIC_SATCOM, PCIS_SATCOM_TV, 1, "sat TV"}, {PCIC_SATCOM, PCIS_SATCOM_AUDIO, 1, "sat audio"}, {PCIC_SATCOM, PCIS_SATCOM_VOICE, 1, "sat voice"}, {PCIC_SATCOM, PCIS_SATCOM_DATA, 1, "sat data"}, {PCIC_CRYPTO, -1, 1, "encrypt/decrypt"}, {PCIC_CRYPTO, PCIS_CRYPTO_NETCOMP, 1, "network/computer crypto"}, {PCIC_CRYPTO, PCIS_CRYPTO_ENTERTAIN, 1, "entertainment crypto"}, {PCIC_DASP, -1, 0, "dasp"}, {PCIC_DASP, PCIS_DASP_DPIO, 1, "DPIO module"}, {0, 0, 0, NULL} }; void pci_probe_nomatch(device_t dev, device_t child) { int i, report; const char *cp, *scp; char *device; /* * Look for a listing for this device in a loaded device database. */ report = 1; if ((device = pci_describe_device(child)) != NULL) { device_printf(dev, "<%s>", device); free(device, M_DEVBUF); } else { /* * Scan the class/subclass descriptions for a general * description. */ cp = "unknown"; scp = NULL; for (i = 0; pci_nomatch_tab[i].desc != NULL; i++) { if (pci_nomatch_tab[i].class == pci_get_class(child)) { if (pci_nomatch_tab[i].subclass == -1) { cp = pci_nomatch_tab[i].desc; report = pci_nomatch_tab[i].report; } else if (pci_nomatch_tab[i].subclass == pci_get_subclass(child)) { scp = pci_nomatch_tab[i].desc; report = pci_nomatch_tab[i].report; } } } if (report || bootverbose) { device_printf(dev, "<%s%s%s>", cp ? cp : "", ((cp != NULL) && (scp != NULL)) ? ", " : "", scp ? scp : ""); } } if (report || bootverbose) { printf(" at device %d.%d (no driver attached)\n", pci_get_slot(child), pci_get_function(child)); } pci_cfg_save(child, device_get_ivars(child), 1); } void pci_child_detached(device_t dev, device_t child) { struct pci_devinfo *dinfo; struct resource_list *rl; dinfo = device_get_ivars(child); rl = &dinfo->resources; /* * Have to deallocate IRQs before releasing any MSI messages and * have to release MSI messages before deallocating any memory * BARs. */ if (resource_list_release_active(rl, dev, child, SYS_RES_IRQ) != 0) pci_printf(&dinfo->cfg, "Device leaked IRQ resources\n"); if (dinfo->cfg.msi.msi_alloc != 0 || dinfo->cfg.msix.msix_alloc != 0) { pci_printf(&dinfo->cfg, "Device leaked MSI vectors\n"); (void)pci_release_msi(child); } if (resource_list_release_active(rl, dev, child, SYS_RES_MEMORY) != 0) pci_printf(&dinfo->cfg, "Device leaked memory resources\n"); if (resource_list_release_active(rl, dev, child, SYS_RES_IOPORT) != 0) pci_printf(&dinfo->cfg, "Device leaked I/O resources\n"); #ifdef PCI_RES_BUS if (resource_list_release_active(rl, dev, child, PCI_RES_BUS) != 0) pci_printf(&dinfo->cfg, "Device leaked PCI bus numbers\n"); #endif pci_cfg_save(child, dinfo, 1); } /* * Parse the PCI device database, if loaded, and return a pointer to a * description of the device. * * The database is flat text formatted as follows: * * Any line not in a valid format is ignored. * Lines are terminated with newline '\n' characters. * * A VENDOR line consists of the 4 digit (hex) vendor code, a TAB, then * the vendor name. * * A DEVICE line is entered immediately below the corresponding VENDOR ID. * - devices cannot be listed without a corresponding VENDOR line. * A DEVICE line consists of a TAB, the 4 digit (hex) device code, * another TAB, then the device name. */ /* * Assuming (ptr) points to the beginning of a line in the database, * return the vendor or device and description of the next entry. * The value of (vendor) or (device) inappropriate for the entry type * is set to -1. Returns nonzero at the end of the database. * * Note that this is slightly unrobust in the face of corrupt data; * we attempt to safeguard against this by spamming the end of the * database with a newline when we initialise. */ static int pci_describe_parse_line(char **ptr, int *vendor, int *device, char **desc) { char *cp = *ptr; int left; *device = -1; *vendor = -1; **desc = '\0'; for (;;) { left = pci_vendordata_size - (cp - pci_vendordata); if (left <= 0) { *ptr = cp; return(1); } /* vendor entry? */ if (*cp != '\t' && sscanf(cp, "%x\t%80[^\n]", vendor, *desc) == 2) break; /* device entry? */ if (*cp == '\t' && sscanf(cp, "%x\t%80[^\n]", device, *desc) == 2) break; /* skip to next line */ while (*cp != '\n' && left > 0) { cp++; left--; } if (*cp == '\n') { cp++; left--; } } /* skip to next line */ while (*cp != '\n' && left > 0) { cp++; left--; } if (*cp == '\n' && left > 0) cp++; *ptr = cp; return(0); } static char * pci_describe_device(device_t dev) { int vendor, device; char *desc, *vp, *dp, *line; desc = vp = dp = NULL; /* * If we have no vendor data, we can't do anything. */ if (pci_vendordata == NULL) goto out; /* * Scan the vendor data looking for this device */ line = pci_vendordata; if ((vp = malloc(80, M_DEVBUF, M_NOWAIT)) == NULL) goto out; for (;;) { if (pci_describe_parse_line(&line, &vendor, &device, &vp)) goto out; if (vendor == pci_get_vendor(dev)) break; } if ((dp = malloc(80, M_DEVBUF, M_NOWAIT)) == NULL) goto out; for (;;) { if (pci_describe_parse_line(&line, &vendor, &device, &dp)) { *dp = 0; break; } if (vendor != -1) { *dp = 0; break; } if (device == pci_get_device(dev)) break; } if (dp[0] == '\0') snprintf(dp, 80, "0x%x", pci_get_device(dev)); if ((desc = malloc(strlen(vp) + strlen(dp) + 3, M_DEVBUF, M_NOWAIT)) != NULL) sprintf(desc, "%s, %s", vp, dp); out: if (vp != NULL) free(vp, M_DEVBUF); if (dp != NULL) free(dp, M_DEVBUF); return(desc); } int pci_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct pci_devinfo *dinfo; pcicfgregs *cfg; dinfo = device_get_ivars(child); cfg = &dinfo->cfg; switch (which) { case PCI_IVAR_ETHADDR: /* * The generic accessor doesn't deal with failure, so * we set the return value, then return an error. */ *((uint8_t **) result) = NULL; return (EINVAL); case PCI_IVAR_SUBVENDOR: *result = cfg->subvendor; break; case PCI_IVAR_SUBDEVICE: *result = cfg->subdevice; break; case PCI_IVAR_VENDOR: *result = cfg->vendor; break; case PCI_IVAR_DEVICE: *result = cfg->device; break; case PCI_IVAR_DEVID: *result = (cfg->device << 16) | cfg->vendor; break; case PCI_IVAR_CLASS: *result = cfg->baseclass; break; case PCI_IVAR_SUBCLASS: *result = cfg->subclass; break; case PCI_IVAR_PROGIF: *result = cfg->progif; break; case PCI_IVAR_REVID: *result = cfg->revid; break; case PCI_IVAR_INTPIN: *result = cfg->intpin; break; case PCI_IVAR_IRQ: *result = cfg->intline; break; case PCI_IVAR_DOMAIN: *result = cfg->domain; break; case PCI_IVAR_BUS: *result = cfg->bus; break; case PCI_IVAR_SLOT: *result = cfg->slot; break; case PCI_IVAR_FUNCTION: *result = cfg->func; break; case PCI_IVAR_CMDREG: *result = cfg->cmdreg; break; case PCI_IVAR_CACHELNSZ: *result = cfg->cachelnsz; break; case PCI_IVAR_MINGNT: *result = cfg->mingnt; break; case PCI_IVAR_MAXLAT: *result = cfg->maxlat; break; case PCI_IVAR_LATTIMER: *result = cfg->lattimer; break; default: return (ENOENT); } return (0); } int pci_write_ivar(device_t dev, device_t child, int which, uintptr_t value) { struct pci_devinfo *dinfo; dinfo = device_get_ivars(child); switch (which) { case PCI_IVAR_INTPIN: dinfo->cfg.intpin = value; return (0); case PCI_IVAR_ETHADDR: case PCI_IVAR_SUBVENDOR: case PCI_IVAR_SUBDEVICE: case PCI_IVAR_VENDOR: case PCI_IVAR_DEVICE: case PCI_IVAR_DEVID: case PCI_IVAR_CLASS: case PCI_IVAR_SUBCLASS: case PCI_IVAR_PROGIF: case PCI_IVAR_REVID: case PCI_IVAR_IRQ: case PCI_IVAR_DOMAIN: case PCI_IVAR_BUS: case PCI_IVAR_SLOT: case PCI_IVAR_FUNCTION: return (EINVAL); /* disallow for now */ default: return (ENOENT); } } #include "opt_ddb.h" #ifdef DDB #include #include /* * List resources based on pci map registers, used for within ddb */ DB_SHOW_COMMAND(pciregs, db_pci_dump) { struct pci_devinfo *dinfo; struct devlist *devlist_head; struct pci_conf *p; const char *name; int i, error, none_count; none_count = 0; /* get the head of the device queue */ devlist_head = &pci_devq; /* * Go through the list of devices and print out devices */ for (error = 0, i = 0, dinfo = STAILQ_FIRST(devlist_head); (dinfo != NULL) && (error == 0) && (i < pci_numdevs) && !db_pager_quit; dinfo = STAILQ_NEXT(dinfo, pci_links), i++) { /* Populate pd_name and pd_unit */ name = NULL; if (dinfo->cfg.dev) name = device_get_name(dinfo->cfg.dev); p = &dinfo->conf; db_printf("%s%d@pci%d:%d:%d:%d:\tclass=0x%06x card=0x%08x " "chip=0x%08x rev=0x%02x hdr=0x%02x\n", (name && *name) ? name : "none", (name && *name) ? (int)device_get_unit(dinfo->cfg.dev) : none_count++, p->pc_sel.pc_domain, p->pc_sel.pc_bus, p->pc_sel.pc_dev, p->pc_sel.pc_func, (p->pc_class << 16) | (p->pc_subclass << 8) | p->pc_progif, (p->pc_subdevice << 16) | p->pc_subvendor, (p->pc_device << 16) | p->pc_vendor, p->pc_revid, p->pc_hdr); } } #endif /* DDB */ static struct resource * pci_reserve_map(device_t dev, device_t child, int type, int *rid, u_long start, u_long end, u_long count, u_int flags) { struct pci_devinfo *dinfo = device_get_ivars(child); struct resource_list *rl = &dinfo->resources; struct resource *res; struct pci_map *pm; pci_addr_t map, testval; int mapsize; res = NULL; pm = pci_find_bar(child, *rid); if (pm != NULL) { /* This is a BAR that we failed to allocate earlier. */ mapsize = pm->pm_size; map = pm->pm_value; } else { /* * Weed out the bogons, and figure out how large the * BAR/map is. BARs that read back 0 here are bogus * and unimplemented. Note: atapci in legacy mode are * special and handled elsewhere in the code. If you * have a atapci device in legacy mode and it fails * here, that other code is broken. */ pci_read_bar(child, *rid, &map, &testval); /* * Determine the size of the BAR and ignore BARs with a size * of 0. Device ROM BARs use a different mask value. */ if (PCIR_IS_BIOS(&dinfo->cfg, *rid)) mapsize = pci_romsize(testval); else mapsize = pci_mapsize(testval); if (mapsize == 0) goto out; pm = pci_add_bar(child, *rid, map, mapsize); } if (PCI_BAR_MEM(map) || PCIR_IS_BIOS(&dinfo->cfg, *rid)) { if (type != SYS_RES_MEMORY) { if (bootverbose) device_printf(dev, "child %s requested type %d for rid %#x," " but the BAR says it is an memio\n", device_get_nameunit(child), type, *rid); goto out; } } else { if (type != SYS_RES_IOPORT) { if (bootverbose) device_printf(dev, "child %s requested type %d for rid %#x," " but the BAR says it is an ioport\n", device_get_nameunit(child), type, *rid); goto out; } } /* * For real BARs, we need to override the size that * the driver requests, because that's what the BAR * actually uses and we would otherwise have a * situation where we might allocate the excess to * another driver, which won't work. */ count = (pci_addr_t)1 << mapsize; if (RF_ALIGNMENT(flags) < mapsize) flags = (flags & ~RF_ALIGNMENT_MASK) | RF_ALIGNMENT_LOG2(mapsize); if (PCI_BAR_MEM(map) && (map & PCIM_BAR_MEM_PREFETCH)) flags |= RF_PREFETCHABLE; /* * Allocate enough resource, and then write back the * appropriate BAR for that resource. */ resource_list_add(rl, type, *rid, start, end, count); res = resource_list_reserve(rl, dev, child, type, rid, start, end, count, flags & ~RF_ACTIVE); if (res == NULL) { resource_list_delete(rl, type, *rid); device_printf(child, "%#lx bytes of rid %#x res %d failed (%#lx, %#lx).\n", count, *rid, type, start, end); goto out; } if (bootverbose) device_printf(child, "Lazy allocation of %#lx bytes rid %#x type %d at %#lx\n", count, *rid, type, rman_get_start(res)); map = rman_get_start(res); pci_write_bar(child, pm, map); out: return (res); } struct resource * pci_alloc_resource(device_t dev, device_t child, int type, int *rid, u_long start, u_long end, u_long count, u_int flags) { struct pci_devinfo *dinfo; struct resource_list *rl; struct resource_list_entry *rle; struct resource *res; pcicfgregs *cfg; if (device_get_parent(child) != dev) return (BUS_ALLOC_RESOURCE(device_get_parent(dev), child, type, rid, start, end, count, flags)); /* * Perform lazy resource allocation */ dinfo = device_get_ivars(child); rl = &dinfo->resources; cfg = &dinfo->cfg; switch (type) { #if defined(NEW_PCIB) && defined(PCI_RES_BUS) case PCI_RES_BUS: return (pci_alloc_secbus(dev, child, rid, start, end, count, flags)); #endif case SYS_RES_IRQ: /* * Can't alloc legacy interrupt once MSI messages have * been allocated. */ if (*rid == 0 && (cfg->msi.msi_alloc > 0 || cfg->msix.msix_alloc > 0)) return (NULL); /* * If the child device doesn't have an interrupt * routed and is deserving of an interrupt, try to * assign it one. */ if (*rid == 0 && !PCI_INTERRUPT_VALID(cfg->intline) && (cfg->intpin != 0)) pci_assign_interrupt(dev, child, 0); break; case SYS_RES_IOPORT: case SYS_RES_MEMORY: #ifdef NEW_PCIB /* * PCI-PCI bridge I/O window resources are not BARs. * For those allocations just pass the request up the * tree. */ if (cfg->hdrtype == PCIM_HDRTYPE_BRIDGE) { switch (*rid) { case PCIR_IOBASEL_1: case PCIR_MEMBASE_1: case PCIR_PMBASEL_1: /* * XXX: Should we bother creating a resource * list entry? */ return (bus_generic_alloc_resource(dev, child, type, rid, start, end, count, flags)); } } #endif /* Reserve resources for this BAR if needed. */ rle = resource_list_find(rl, type, *rid); if (rle == NULL) { res = pci_reserve_map(dev, child, type, rid, start, end, count, flags); if (res == NULL) return (NULL); } } return (resource_list_alloc(rl, dev, child, type, rid, start, end, count, flags)); } int pci_release_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { struct pci_devinfo *dinfo; struct resource_list *rl; pcicfgregs *cfg; if (device_get_parent(child) != dev) return (BUS_RELEASE_RESOURCE(device_get_parent(dev), child, type, rid, r)); dinfo = device_get_ivars(child); cfg = &dinfo->cfg; #ifdef NEW_PCIB /* * PCI-PCI bridge I/O window resources are not BARs. For * those allocations just pass the request up the tree. */ if (cfg->hdrtype == PCIM_HDRTYPE_BRIDGE && (type == SYS_RES_IOPORT || type == SYS_RES_MEMORY)) { switch (rid) { case PCIR_IOBASEL_1: case PCIR_MEMBASE_1: case PCIR_PMBASEL_1: return (bus_generic_release_resource(dev, child, type, rid, r)); } } #endif rl = &dinfo->resources; return (resource_list_release(rl, dev, child, type, rid, r)); } int pci_activate_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { struct pci_devinfo *dinfo; int error; error = bus_generic_activate_resource(dev, child, type, rid, r); if (error) return (error); /* Enable decoding in the command register when activating BARs. */ if (device_get_parent(child) == dev) { /* Device ROMs need their decoding explicitly enabled. */ dinfo = device_get_ivars(child); if (type == SYS_RES_MEMORY && PCIR_IS_BIOS(&dinfo->cfg, rid)) pci_write_bar(child, pci_find_bar(child, rid), rman_get_start(r) | PCIM_BIOS_ENABLE); switch (type) { case SYS_RES_IOPORT: case SYS_RES_MEMORY: error = PCI_ENABLE_IO(dev, child, type); break; } } return (error); } int pci_deactivate_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { struct pci_devinfo *dinfo; int error; error = bus_generic_deactivate_resource(dev, child, type, rid, r); if (error) return (error); /* Disable decoding for device ROMs. */ if (device_get_parent(child) == dev) { dinfo = device_get_ivars(child); if (type == SYS_RES_MEMORY && PCIR_IS_BIOS(&dinfo->cfg, rid)) pci_write_bar(child, pci_find_bar(child, rid), rman_get_start(r)); } return (0); } void pci_delete_child(device_t dev, device_t child) { struct resource_list_entry *rle; struct resource_list *rl; struct pci_devinfo *dinfo; dinfo = device_get_ivars(child); rl = &dinfo->resources; if (device_is_attached(child)) device_detach(child); /* Turn off access to resources we're about to free */ pci_write_config(child, PCIR_COMMAND, pci_read_config(child, PCIR_COMMAND, 2) & ~(PCIM_CMD_MEMEN | PCIM_CMD_PORTEN), 2); /* Free all allocated resources */ STAILQ_FOREACH(rle, rl, link) { if (rle->res) { if (rman_get_flags(rle->res) & RF_ACTIVE || resource_list_busy(rl, rle->type, rle->rid)) { pci_printf(&dinfo->cfg, "Resource still owned, oops. " "(type=%d, rid=%d, addr=%lx)\n", rle->type, rle->rid, rman_get_start(rle->res)); bus_release_resource(child, rle->type, rle->rid, rle->res); } resource_list_unreserve(rl, dev, child, rle->type, rle->rid); } } resource_list_free(rl); device_delete_child(dev, child); pci_freecfg(dinfo); } void pci_delete_resource(device_t dev, device_t child, int type, int rid) { struct pci_devinfo *dinfo; struct resource_list *rl; struct resource_list_entry *rle; if (device_get_parent(child) != dev) return; dinfo = device_get_ivars(child); rl = &dinfo->resources; rle = resource_list_find(rl, type, rid); if (rle == NULL) return; if (rle->res) { if (rman_get_flags(rle->res) & RF_ACTIVE || resource_list_busy(rl, type, rid)) { device_printf(dev, "delete_resource: " "Resource still owned by child, oops. " "(type=%d, rid=%d, addr=%lx)\n", type, rid, rman_get_start(rle->res)); return; } resource_list_unreserve(rl, dev, child, type, rid); } resource_list_delete(rl, type, rid); } struct resource_list * pci_get_resource_list (device_t dev, device_t child) { struct pci_devinfo *dinfo = device_get_ivars(child); return (&dinfo->resources); } bus_dma_tag_t pci_get_dma_tag(device_t bus, device_t dev) { struct pci_softc *sc = device_get_softc(bus); return (sc->sc_dma_tag); } uint32_t pci_read_config_method(device_t dev, device_t child, int reg, int width) { struct pci_devinfo *dinfo = device_get_ivars(child); pcicfgregs *cfg = &dinfo->cfg; return (PCIB_READ_CONFIG(device_get_parent(dev), cfg->bus, cfg->slot, cfg->func, reg, width)); } void pci_write_config_method(device_t dev, device_t child, int reg, uint32_t val, int width) { struct pci_devinfo *dinfo = device_get_ivars(child); pcicfgregs *cfg = &dinfo->cfg; PCIB_WRITE_CONFIG(device_get_parent(dev), cfg->bus, cfg->slot, cfg->func, reg, val, width); } int pci_child_location_str_method(device_t dev, device_t child, char *buf, size_t buflen) { snprintf(buf, buflen, "slot=%d function=%d", pci_get_slot(child), pci_get_function(child)); return (0); } int pci_child_pnpinfo_str_method(device_t dev, device_t child, char *buf, size_t buflen) { struct pci_devinfo *dinfo; pcicfgregs *cfg; dinfo = device_get_ivars(child); cfg = &dinfo->cfg; snprintf(buf, buflen, "vendor=0x%04x device=0x%04x subvendor=0x%04x " "subdevice=0x%04x class=0x%02x%02x%02x", cfg->vendor, cfg->device, cfg->subvendor, cfg->subdevice, cfg->baseclass, cfg->subclass, cfg->progif); return (0); } int pci_assign_interrupt_method(device_t dev, device_t child) { struct pci_devinfo *dinfo = device_get_ivars(child); pcicfgregs *cfg = &dinfo->cfg; return (PCIB_ROUTE_INTERRUPT(device_get_parent(dev), child, cfg->intpin)); } static int pci_modevent(module_t mod, int what, void *arg) { static struct cdev *pci_cdev; switch (what) { case MOD_LOAD: STAILQ_INIT(&pci_devq); pci_generation = 0; pci_cdev = make_dev(&pcicdev, 0, UID_ROOT, GID_WHEEL, 0644, "pci"); pci_load_vendor_data(); break; case MOD_UNLOAD: destroy_dev(pci_cdev); break; } return (0); } static void pci_cfg_restore_pcie(device_t dev, struct pci_devinfo *dinfo) { #define WREG(n, v) pci_write_config(dev, pos + (n), (v), 2) struct pcicfg_pcie *cfg; int version, pos; cfg = &dinfo->cfg.pcie; pos = cfg->pcie_location; version = cfg->pcie_flags & PCIEM_FLAGS_VERSION; WREG(PCIER_DEVICE_CTL, cfg->pcie_device_ctl); if (version > 1 || cfg->pcie_type == PCIEM_TYPE_ROOT_PORT || cfg->pcie_type == PCIEM_TYPE_ENDPOINT || cfg->pcie_type == PCIEM_TYPE_LEGACY_ENDPOINT) WREG(PCIER_LINK_CTL, cfg->pcie_link_ctl); if (version > 1 || (cfg->pcie_type == PCIEM_TYPE_ROOT_PORT || (cfg->pcie_type == PCIEM_TYPE_DOWNSTREAM_PORT && (cfg->pcie_flags & PCIEM_FLAGS_SLOT)))) WREG(PCIER_SLOT_CTL, cfg->pcie_slot_ctl); if (version > 1 || cfg->pcie_type == PCIEM_TYPE_ROOT_PORT || cfg->pcie_type == PCIEM_TYPE_ROOT_EC) WREG(PCIER_ROOT_CTL, cfg->pcie_root_ctl); if (version > 1) { WREG(PCIER_DEVICE_CTL2, cfg->pcie_device_ctl2); WREG(PCIER_LINK_CTL2, cfg->pcie_link_ctl2); WREG(PCIER_SLOT_CTL2, cfg->pcie_slot_ctl2); } #undef WREG } static void pci_cfg_restore_pcix(device_t dev, struct pci_devinfo *dinfo) { pci_write_config(dev, dinfo->cfg.pcix.pcix_location + PCIXR_COMMAND, dinfo->cfg.pcix.pcix_command, 2); } void pci_cfg_restore(device_t dev, struct pci_devinfo *dinfo) { /* * Only do header type 0 devices. Type 1 devices are bridges, * which we know need special treatment. Type 2 devices are * cardbus bridges which also require special treatment. * Other types are unknown, and we err on the side of safety * by ignoring them. */ if ((dinfo->cfg.hdrtype & PCIM_HDRTYPE) != PCIM_HDRTYPE_NORMAL) return; /* * Restore the device to full power mode. We must do this * before we restore the registers because moving from D3 to * D0 will cause the chip's BARs and some other registers to * be reset to some unknown power on reset values. Cut down * the noise on boot by doing nothing if we are already in * state D0. */ if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0) pci_set_powerstate(dev, PCI_POWERSTATE_D0); pci_restore_bars(dev); pci_write_config(dev, PCIR_COMMAND, dinfo->cfg.cmdreg, 2); pci_write_config(dev, PCIR_INTLINE, dinfo->cfg.intline, 1); pci_write_config(dev, PCIR_INTPIN, dinfo->cfg.intpin, 1); pci_write_config(dev, PCIR_MINGNT, dinfo->cfg.mingnt, 1); pci_write_config(dev, PCIR_MAXLAT, dinfo->cfg.maxlat, 1); pci_write_config(dev, PCIR_CACHELNSZ, dinfo->cfg.cachelnsz, 1); pci_write_config(dev, PCIR_LATTIMER, dinfo->cfg.lattimer, 1); pci_write_config(dev, PCIR_PROGIF, dinfo->cfg.progif, 1); pci_write_config(dev, PCIR_REVID, dinfo->cfg.revid, 1); /* * Restore extended capabilities for PCI-Express and PCI-X */ if (dinfo->cfg.pcie.pcie_location != 0) pci_cfg_restore_pcie(dev, dinfo); if (dinfo->cfg.pcix.pcix_location != 0) pci_cfg_restore_pcix(dev, dinfo); /* Restore MSI and MSI-X configurations if they are present. */ if (dinfo->cfg.msi.msi_location != 0) pci_resume_msi(dev); if (dinfo->cfg.msix.msix_location != 0) pci_resume_msix(dev); } static void pci_cfg_save_pcie(device_t dev, struct pci_devinfo *dinfo) { #define RREG(n) pci_read_config(dev, pos + (n), 2) struct pcicfg_pcie *cfg; int version, pos; cfg = &dinfo->cfg.pcie; pos = cfg->pcie_location; cfg->pcie_flags = RREG(PCIER_FLAGS); version = cfg->pcie_flags & PCIEM_FLAGS_VERSION; cfg->pcie_device_ctl = RREG(PCIER_DEVICE_CTL); if (version > 1 || cfg->pcie_type == PCIEM_TYPE_ROOT_PORT || cfg->pcie_type == PCIEM_TYPE_ENDPOINT || cfg->pcie_type == PCIEM_TYPE_LEGACY_ENDPOINT) cfg->pcie_link_ctl = RREG(PCIER_LINK_CTL); if (version > 1 || (cfg->pcie_type == PCIEM_TYPE_ROOT_PORT || (cfg->pcie_type == PCIEM_TYPE_DOWNSTREAM_PORT && (cfg->pcie_flags & PCIEM_FLAGS_SLOT)))) cfg->pcie_slot_ctl = RREG(PCIER_SLOT_CTL); if (version > 1 || cfg->pcie_type == PCIEM_TYPE_ROOT_PORT || cfg->pcie_type == PCIEM_TYPE_ROOT_EC) cfg->pcie_root_ctl = RREG(PCIER_ROOT_CTL); if (version > 1) { cfg->pcie_device_ctl2 = RREG(PCIER_DEVICE_CTL2); cfg->pcie_link_ctl2 = RREG(PCIER_LINK_CTL2); cfg->pcie_slot_ctl2 = RREG(PCIER_SLOT_CTL2); } #undef RREG } static void pci_cfg_save_pcix(device_t dev, struct pci_devinfo *dinfo) { dinfo->cfg.pcix.pcix_command = pci_read_config(dev, dinfo->cfg.pcix.pcix_location + PCIXR_COMMAND, 2); } void pci_cfg_save(device_t dev, struct pci_devinfo *dinfo, int setstate) { uint32_t cls; int ps; /* * Only do header type 0 devices. Type 1 devices are bridges, which * we know need special treatment. Type 2 devices are cardbus bridges * which also require special treatment. Other types are unknown, and * we err on the side of safety by ignoring them. Powering down * bridges should not be undertaken lightly. */ if ((dinfo->cfg.hdrtype & PCIM_HDRTYPE) != PCIM_HDRTYPE_NORMAL) return; /* * Some drivers apparently write to these registers w/o updating our * cached copy. No harm happens if we update the copy, so do so here * so we can restore them. The COMMAND register is modified by the * bus w/o updating the cache. This should represent the normally * writable portion of the 'defined' part of type 0 headers. In * theory we also need to save/restore the PCI capability structures * we know about, but apart from power we don't know any that are * writable. */ dinfo->cfg.subvendor = pci_read_config(dev, PCIR_SUBVEND_0, 2); dinfo->cfg.subdevice = pci_read_config(dev, PCIR_SUBDEV_0, 2); dinfo->cfg.vendor = pci_read_config(dev, PCIR_VENDOR, 2); dinfo->cfg.device = pci_read_config(dev, PCIR_DEVICE, 2); dinfo->cfg.cmdreg = pci_read_config(dev, PCIR_COMMAND, 2); dinfo->cfg.intline = pci_read_config(dev, PCIR_INTLINE, 1); dinfo->cfg.intpin = pci_read_config(dev, PCIR_INTPIN, 1); dinfo->cfg.mingnt = pci_read_config(dev, PCIR_MINGNT, 1); dinfo->cfg.maxlat = pci_read_config(dev, PCIR_MAXLAT, 1); dinfo->cfg.cachelnsz = pci_read_config(dev, PCIR_CACHELNSZ, 1); dinfo->cfg.lattimer = pci_read_config(dev, PCIR_LATTIMER, 1); dinfo->cfg.baseclass = pci_read_config(dev, PCIR_CLASS, 1); dinfo->cfg.subclass = pci_read_config(dev, PCIR_SUBCLASS, 1); dinfo->cfg.progif = pci_read_config(dev, PCIR_PROGIF, 1); dinfo->cfg.revid = pci_read_config(dev, PCIR_REVID, 1); if (dinfo->cfg.pcie.pcie_location != 0) pci_cfg_save_pcie(dev, dinfo); if (dinfo->cfg.pcix.pcix_location != 0) pci_cfg_save_pcix(dev, dinfo); /* * don't set the state for display devices, base peripherals and * memory devices since bad things happen when they are powered down. * We should (a) have drivers that can easily detach and (b) use * generic drivers for these devices so that some device actually * attaches. We need to make sure that when we implement (a) we don't * power the device down on a reattach. */ cls = pci_get_class(dev); if (!setstate) return; switch (pci_do_power_nodriver) { case 0: /* NO powerdown at all */ return; case 1: /* Conservative about what to power down */ if (cls == PCIC_STORAGE) return; /*FALLTHROUGH*/ case 2: /* Agressive about what to power down */ if (cls == PCIC_DISPLAY || cls == PCIC_MEMORY || cls == PCIC_BASEPERIPH) return; /*FALLTHROUGH*/ case 3: /* Power down everything */ break; } /* * PCI spec says we can only go into D3 state from D0 state. * Transition from D[12] into D0 before going to D3 state. */ ps = pci_get_powerstate(dev); if (ps != PCI_POWERSTATE_D0 && ps != PCI_POWERSTATE_D3) pci_set_powerstate(dev, PCI_POWERSTATE_D0); if (pci_get_powerstate(dev) != PCI_POWERSTATE_D3) pci_set_powerstate(dev, PCI_POWERSTATE_D3); } /* Wrapper APIs suitable for device driver use. */ void pci_save_state(device_t dev) { struct pci_devinfo *dinfo; dinfo = device_get_ivars(dev); pci_cfg_save(dev, dinfo, 0); } void pci_restore_state(device_t dev) { struct pci_devinfo *dinfo; dinfo = device_get_ivars(dev); pci_cfg_restore(dev, dinfo); } static uint16_t pci_get_rid_method(device_t dev, device_t child) { return (PCIB_GET_RID(device_get_parent(dev), child)); } Index: projects/sendfile/sys/kern/kern_cons.c =================================================================== --- projects/sendfile/sys/kern/kern_cons.c (revision 274716) +++ projects/sendfile/sys/kern/kern_cons.c (revision 274717) @@ -1,719 +1,726 @@ /*- * Copyright (c) 1988 University of Utah. * Copyright (c) 1991 The Regents of the University of California. * Copyright (c) 1999 Michael Smith * Copyright (c) 2005 Pawel Jakub Dawidek * * All rights reserved. * * This code is derived from software contributed to Berkeley by * the Systems Programming Group of the University of Utah Computer * Science Department. * * 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. * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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. * * from: @(#)cons.c 7.2 (Berkeley) 5/9/91 */ #include __FBSDID("$FreeBSD$"); #include "opt_ddb.h" #include "opt_syscons.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static MALLOC_DEFINE(M_TTYCONS, "tty console", "tty console handling"); struct cn_device { STAILQ_ENTRY(cn_device) cnd_next; struct consdev *cnd_cn; }; #define CNDEVPATHMAX 32 #define CNDEVTAB_SIZE 4 static struct cn_device cn_devtab[CNDEVTAB_SIZE]; static STAILQ_HEAD(, cn_device) cn_devlist = STAILQ_HEAD_INITIALIZER(cn_devlist); int cons_avail_mask = 0; /* Bit mask. Each registered low level console * which is currently unavailable for inpit * (i.e., if it is in graphics mode) will have * this bit cleared. */ static int cn_mute; static char *consbuf; /* buffer used by `consmsgbuf' */ static struct callout conscallout; /* callout for outputting to constty */ struct msgbuf consmsgbuf; /* message buffer for console tty */ static u_char console_pausing; /* pause after each line during probe */ static char *console_pausestr= ""; struct tty *constty; /* pointer to console "window" tty */ static struct mtx cnputs_mtx; /* Mutex for cnputs(). */ static int use_cnputs_mtx = 0; /* != 0 if cnputs_mtx locking reqd. */ static void constty_timeout(void *arg); static struct consdev cons_consdev; DATA_SET(cons_set, cons_consdev); SET_DECLARE(cons_set, struct consdev); void cninit(void) { struct consdev *best_cn, *cn, **list; /* * Check if we should mute the console (for security reasons perhaps) * It can be changes dynamically using sysctl kern.consmute * once we are up and going. * */ cn_mute = ((boothowto & (RB_MUTE |RB_SINGLE |RB_VERBOSE |RB_ASKNAME)) == RB_MUTE); /* * Find the first console with the highest priority. */ best_cn = NULL; SET_FOREACH(list, cons_set) { cn = *list; cnremove(cn); /* Skip cons_consdev. */ if (cn->cn_ops == NULL) continue; cn->cn_ops->cn_probe(cn); if (cn->cn_pri == CN_DEAD) continue; if (best_cn == NULL || cn->cn_pri > best_cn->cn_pri) best_cn = cn; if (boothowto & RB_MULTIPLE) { /* * Initialize console, and attach to it. */ cn->cn_ops->cn_init(cn); cnadd(cn); } } if (best_cn == NULL) return; if ((boothowto & RB_MULTIPLE) == 0) { best_cn->cn_ops->cn_init(best_cn); cnadd(best_cn); } if (boothowto & RB_PAUSE) console_pausing = 1; /* * Make the best console the preferred console. */ cnselect(best_cn); + +#ifdef EARLY_PRINTF + /* + * Release early console. + */ + early_putc = NULL; +#endif } void cninit_finish() { console_pausing = 0; } /* add a new physical console to back the virtual console */ int cnadd(struct consdev *cn) { struct cn_device *cnd; int i; STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) if (cnd->cnd_cn == cn) return (0); for (i = 0; i < CNDEVTAB_SIZE; i++) { cnd = &cn_devtab[i]; if (cnd->cnd_cn == NULL) break; } if (cnd->cnd_cn != NULL) return (ENOMEM); cnd->cnd_cn = cn; if (cn->cn_name[0] == '\0') { /* XXX: it is unclear if/where this print might output */ printf("WARNING: console at %p has no name\n", cn); } STAILQ_INSERT_TAIL(&cn_devlist, cnd, cnd_next); if (STAILQ_FIRST(&cn_devlist) == cnd) ttyconsdev_select(cnd->cnd_cn->cn_name); /* Add device to the active mask. */ cnavailable(cn, (cn->cn_flags & CN_FLAG_NOAVAIL) == 0); return (0); } void cnremove(struct consdev *cn) { struct cn_device *cnd; int i; STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { if (cnd->cnd_cn != cn) continue; if (STAILQ_FIRST(&cn_devlist) == cnd) ttyconsdev_select(NULL); STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next); cnd->cnd_cn = NULL; /* Remove this device from available mask. */ for (i = 0; i < CNDEVTAB_SIZE; i++) if (cnd == &cn_devtab[i]) { cons_avail_mask &= ~(1 << i); break; } #if 0 /* * XXX * syscons gets really confused if console resources are * freed after the system has initialized. */ if (cn->cn_term != NULL) cn->cn_ops->cn_term(cn); #endif return; } } void cnselect(struct consdev *cn) { struct cn_device *cnd; STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { if (cnd->cnd_cn != cn) continue; if (cnd == STAILQ_FIRST(&cn_devlist)) return; STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next); STAILQ_INSERT_HEAD(&cn_devlist, cnd, cnd_next); ttyconsdev_select(cnd->cnd_cn->cn_name); return; } } void cnavailable(struct consdev *cn, int available) { int i; for (i = 0; i < CNDEVTAB_SIZE; i++) { if (cn_devtab[i].cnd_cn == cn) break; } if (available) { if (i < CNDEVTAB_SIZE) cons_avail_mask |= (1 << i); cn->cn_flags &= ~CN_FLAG_NOAVAIL; } else { if (i < CNDEVTAB_SIZE) cons_avail_mask &= ~(1 << i); cn->cn_flags |= CN_FLAG_NOAVAIL; } } int cnunavailable(void) { return (cons_avail_mask == 0); } /* * sysctl_kern_console() provides output parseable in conscontrol(1). */ static int sysctl_kern_console(SYSCTL_HANDLER_ARGS) { struct cn_device *cnd; struct consdev *cp, **list; char *p; int delete, error; struct sbuf *sb; sb = sbuf_new(NULL, NULL, CNDEVPATHMAX * 2, SBUF_AUTOEXTEND); if (sb == NULL) return (ENOMEM); sbuf_clear(sb); STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) sbuf_printf(sb, "%s,", cnd->cnd_cn->cn_name); sbuf_printf(sb, "/"); SET_FOREACH(list, cons_set) { cp = *list; if (cp->cn_name[0] != '\0') sbuf_printf(sb, "%s,", cp->cn_name); } sbuf_finish(sb); error = sysctl_handle_string(oidp, sbuf_data(sb), sbuf_len(sb), req); if (error == 0 && req->newptr != NULL) { p = sbuf_data(sb); error = ENXIO; delete = 0; if (*p == '-') { delete = 1; p++; } SET_FOREACH(list, cons_set) { cp = *list; if (strcmp(p, cp->cn_name) != 0) continue; if (delete) { cnremove(cp); error = 0; } else { error = cnadd(cp); if (error == 0) cnselect(cp); } break; } } sbuf_delete(sb); return (error); } SYSCTL_PROC(_kern, OID_AUTO, console, CTLTYPE_STRING|CTLFLAG_RW, 0, 0, sysctl_kern_console, "A", "Console device control"); /* * User has changed the state of the console muting. * This may require us to open or close the device in question. */ static int sysctl_kern_consmute(SYSCTL_HANDLER_ARGS) { int error; error = sysctl_handle_int(oidp, &cn_mute, 0, req); if (error != 0 || req->newptr == NULL) return (error); return (error); } SYSCTL_PROC(_kern, OID_AUTO, consmute, CTLTYPE_INT|CTLFLAG_RW, 0, sizeof(cn_mute), sysctl_kern_consmute, "I", "State of the console muting"); void cngrab() { struct cn_device *cnd; struct consdev *cn; STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { cn = cnd->cnd_cn; if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) cn->cn_ops->cn_grab(cn); } } void cnungrab() { struct cn_device *cnd; struct consdev *cn; STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { cn = cnd->cnd_cn; if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) cn->cn_ops->cn_ungrab(cn); } } /* * Low level console routines. */ int cngetc(void) { int c; if (cn_mute) return (-1); while ((c = cncheckc()) == -1) cpu_spinwait(); if (c == '\r') c = '\n'; /* console input is always ICRNL */ return (c); } int cncheckc(void) { struct cn_device *cnd; struct consdev *cn; int c; if (cn_mute) return (-1); STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { cn = cnd->cnd_cn; if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) { c = cn->cn_ops->cn_getc(cn); if (c != -1) return (c); } } return (-1); } void cngets(char *cp, size_t size, int visible) { char *lp, *end; int c; cngrab(); lp = cp; end = cp + size - 1; for (;;) { c = cngetc() & 0177; switch (c) { case '\n': case '\r': cnputc(c); *lp = '\0'; cnungrab(); return; case '\b': case '\177': if (lp > cp) { if (visible) cnputs("\b \b"); lp--; } continue; case '\0': continue; default: if (lp < end) { switch (visible) { case GETS_NOECHO: break; case GETS_ECHOPASS: cnputc('*'); break; default: cnputc(c); break; } *lp++ = c; } } } } void cnputc(int c) { struct cn_device *cnd; struct consdev *cn; char *cp; #ifdef EARLY_PRINTF if (early_putc != NULL) { if (c == '\n') early_putc('\r'); early_putc(c); return; } #endif if (cn_mute || c == '\0') return; STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { cn = cnd->cnd_cn; if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) { if (c == '\n') cn->cn_ops->cn_putc(cn, '\r'); cn->cn_ops->cn_putc(cn, c); } } if (console_pausing && c == '\n' && !kdb_active) { for (cp = console_pausestr; *cp != '\0'; cp++) cnputc(*cp); cngrab(); if (cngetc() == '.') console_pausing = 0; cnungrab(); cnputc('\r'); for (cp = console_pausestr; *cp != '\0'; cp++) cnputc(' '); cnputc('\r'); } } void cnputs(char *p) { int c; int unlock_reqd = 0; if (use_cnputs_mtx) { mtx_lock_spin(&cnputs_mtx); unlock_reqd = 1; } while ((c = *p++) != '\0') cnputc(c); if (unlock_reqd) mtx_unlock_spin(&cnputs_mtx); } static int consmsgbuf_size = 8192; SYSCTL_INT(_kern, OID_AUTO, consmsgbuf_size, CTLFLAG_RW, &consmsgbuf_size, 0, "Console tty buffer size"); /* * Redirect console output to a tty. */ void constty_set(struct tty *tp) { int size; KASSERT(tp != NULL, ("constty_set: NULL tp")); if (consbuf == NULL) { size = consmsgbuf_size; consbuf = malloc(size, M_TTYCONS, M_WAITOK); msgbuf_init(&consmsgbuf, consbuf, size); callout_init(&conscallout, 0); } constty = tp; constty_timeout(NULL); } /* * Disable console redirection to a tty. */ void constty_clear(void) { int c; constty = NULL; if (consbuf == NULL) return; callout_stop(&conscallout); while ((c = msgbuf_getchar(&consmsgbuf)) != -1) cnputc(c); free(consbuf, M_TTYCONS); consbuf = NULL; } /* Times per second to check for pending console tty messages. */ static int constty_wakeups_per_second = 5; SYSCTL_INT(_kern, OID_AUTO, constty_wakeups_per_second, CTLFLAG_RW, &constty_wakeups_per_second, 0, "Times per second to check for pending console tty messages"); static void constty_timeout(void *arg) { int c; if (constty != NULL) { tty_lock(constty); while ((c = msgbuf_getchar(&consmsgbuf)) != -1) { if (tty_putchar(constty, c) < 0) { tty_unlock(constty); constty = NULL; break; } } if (constty != NULL) tty_unlock(constty); } if (constty != NULL) { callout_reset(&conscallout, hz / constty_wakeups_per_second, constty_timeout, NULL); } else { /* Deallocate the constty buffer memory. */ constty_clear(); } } static void cn_drvinit(void *unused) { mtx_init(&cnputs_mtx, "cnputs_mtx", NULL, MTX_SPIN | MTX_NOWITNESS); use_cnputs_mtx = 1; } SYSINIT(cndev, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, cn_drvinit, NULL); /* * Sysbeep(), if we have hardware for it */ #ifdef HAS_TIMER_SPKR static int beeping; static struct callout beeping_timer; static void sysbeepstop(void *chan) { timer_spkr_release(); beeping = 0; } int sysbeep(int pitch, int period) { if (timer_spkr_acquire()) { if (!beeping) { /* Something else owns it. */ return (EBUSY); } } timer_spkr_setfreq(pitch); if (!beeping) { beeping = period; callout_reset(&beeping_timer, period, sysbeepstop, NULL); } return (0); } static void sysbeep_init(void *unused) { callout_init(&beeping_timer, CALLOUT_MPSAFE); } SYSINIT(sysbeep, SI_SUB_SOFTINTR, SI_ORDER_ANY, sysbeep_init, NULL); #else /* * No hardware, no sound */ int sysbeep(int pitch __unused, int period __unused) { return (ENODEV); } #endif /* * Temporary support for sc(4) to vt(4) transition. */ static unsigned vty_prefer; static char vty_name[16]; SYSCTL_STRING(_kern, OID_AUTO, vty, CTLFLAG_RDTUN | CTLFLAG_NOFETCH, vty_name, 0, "Console vty driver"); int vty_enabled(unsigned vty) { static unsigned vty_selected = 0; if (vty_selected == 0) { TUNABLE_STR_FETCH("kern.vty", vty_name, sizeof(vty_name)); do { #if defined(DEV_SC) if (strcmp(vty_name, "sc") == 0) { vty_selected = VTY_SC; break; } #endif #if defined(DEV_VT) if (strcmp(vty_name, "vt") == 0) { vty_selected = VTY_VT; break; } #endif if (vty_prefer != 0) { vty_selected = vty_prefer; break; } #if defined(DEV_VT) vty_selected = VTY_VT; #elif defined(DEV_SC) vty_selected = VTY_SC; #endif } while (0); if (vty_selected == VTY_VT) strcpy(vty_name, "vt"); else if (vty_selected == VTY_SC) strcpy(vty_name, "sc"); } return ((vty_selected & vty) != 0); } void vty_set_preferred(unsigned vty) { vty_prefer = vty; #if !defined(DEV_SC) vty_prefer &= ~VTY_SC; #endif #if !defined(DEV_VT) vty_prefer &= ~VTY_VT; #endif } Index: projects/sendfile/sys/kern/uipc_socket.c =================================================================== --- projects/sendfile/sys/kern/uipc_socket.c (revision 274716) +++ projects/sendfile/sys/kern/uipc_socket.c (revision 274717) @@ -1,3705 +1,3705 @@ /*- * Copyright (c) 1982, 1986, 1988, 1990, 1993 * The Regents of the University of California. * Copyright (c) 2004 The FreeBSD Foundation * Copyright (c) 2004-2008 Robert N. M. Watson * All rights reserved. * * 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. * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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. * * @(#)uipc_socket.c 8.3 (Berkeley) 4/15/94 */ /* * Comments on the socket life cycle: * * soalloc() sets of socket layer state for a socket, called only by * socreate() and sonewconn(). Socket layer private. * * sodealloc() tears down socket layer state for a socket, called only by * sofree() and sonewconn(). Socket layer private. * * pru_attach() associates protocol layer state with an allocated socket; * called only once, may fail, aborting socket allocation. This is called * from socreate() and sonewconn(). Socket layer private. * * pru_detach() disassociates protocol layer state from an attached socket, * and will be called exactly once for sockets in which pru_attach() has * been successfully called. If pru_attach() returned an error, * pru_detach() will not be called. Socket layer private. * * pru_abort() and pru_close() notify the protocol layer that the last * consumer of a socket is starting to tear down the socket, and that the * protocol should terminate the connection. Historically, pru_abort() also * detached protocol state from the socket state, but this is no longer the * case. * * socreate() creates a socket and attaches protocol state. This is a public * interface that may be used by socket layer consumers to create new * sockets. * * sonewconn() creates a socket and attaches protocol state. This is a * public interface that may be used by protocols to create new sockets when * a new connection is received and will be available for accept() on a * listen socket. * * soclose() destroys a socket after possibly waiting for it to disconnect. * This is a public interface that socket consumers should use to close and * release a socket when done with it. * * soabort() destroys a socket without waiting for it to disconnect (used * only for incoming connections that are already partially or fully * connected). This is used internally by the socket layer when clearing * listen socket queues (due to overflow or close on the listen socket), but * is also a public interface protocols may use to abort connections in * their incomplete listen queues should they no longer be required. Sockets * placed in completed connection listen queues should not be aborted for * reasons described in the comment above the soclose() implementation. This * is not a general purpose close routine, and except in the specific * circumstances described here, should not be used. * * sofree() will free a socket and its protocol state if all references on * the socket have been released, and is the public interface to attempt to * free a socket when a reference is removed. This is a socket layer private * interface. * * NOTE: In addition to socreate() and soclose(), which provide a single * socket reference to the consumer to be managed as required, there are two * calls to explicitly manage socket references, soref(), and sorele(). * Currently, these are generally required only when transitioning a socket * from a listen queue to a file descriptor, in order to prevent garbage * collection of the socket at an untimely moment. For a number of reasons, * these interfaces are not preferred, and should be avoided. * * NOTE: With regard to VNETs the general rule is that callers do not set * curvnet. Exceptions to this rule include soabort(), sodisconnect(), * sofree() (and with that sorele(), sotryfree()), as well as sonewconn() * and sorflush(), which are usually called from a pre-set VNET context. * sopoll() currently does not need a VNET context to be set. */ #include __FBSDID("$FreeBSD$"); #include "opt_inet.h" #include "opt_inet6.h" #include "opt_compat.h" #include #include #include #include #include #include #include #include #include #include #include /* for struct knote */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef COMPAT_FREEBSD32 #include #include #include #endif static int soreceive_rcvoob(struct socket *so, struct uio *uio, int flags); static void filt_sordetach(struct knote *kn); static int filt_soread(struct knote *kn, long hint); static void filt_sowdetach(struct knote *kn); static int filt_sowrite(struct knote *kn, long hint); static int filt_solisten(struct knote *kn, long hint); static int inline hhook_run_socket(struct socket *so, void *hctx, int32_t h_id); fo_kqfilter_t soo_kqfilter; static struct filterops solisten_filtops = { .f_isfd = 1, .f_detach = filt_sordetach, .f_event = filt_solisten, }; static struct filterops soread_filtops = { .f_isfd = 1, .f_detach = filt_sordetach, .f_event = filt_soread, }; static struct filterops sowrite_filtops = { .f_isfd = 1, .f_detach = filt_sowdetach, .f_event = filt_sowrite, }; so_gen_t so_gencnt; /* generation count for sockets */ MALLOC_DEFINE(M_SONAME, "soname", "socket name"); MALLOC_DEFINE(M_PCB, "pcb", "protocol control block"); #define VNET_SO_ASSERT(so) \ VNET_ASSERT(curvnet != NULL, \ ("%s:%d curvnet is NULL, so=%p", __func__, __LINE__, (so))); VNET_DEFINE(struct hhook_head *, socket_hhh[HHOOK_SOCKET_LAST + 1]); #define V_socket_hhh VNET(socket_hhh) /* * Limit on the number of connections in the listen queue waiting * for accept(2). * NB: The orginal sysctl somaxconn is still available but hidden * to prevent confusion about the actual purpose of this number. */ static int somaxconn = SOMAXCONN; static int sysctl_somaxconn(SYSCTL_HANDLER_ARGS) { int error; int val; val = somaxconn; error = sysctl_handle_int(oidp, &val, 0, req); if (error || !req->newptr ) return (error); if (val < 1 || val > USHRT_MAX) return (EINVAL); somaxconn = val; return (0); } SYSCTL_PROC(_kern_ipc, OID_AUTO, soacceptqueue, CTLTYPE_UINT | CTLFLAG_RW, 0, sizeof(int), sysctl_somaxconn, "I", "Maximum listen socket pending connection accept queue size"); SYSCTL_PROC(_kern_ipc, KIPC_SOMAXCONN, somaxconn, CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_SKIP, 0, sizeof(int), sysctl_somaxconn, "I", "Maximum listen socket pending connection accept queue size (compat)"); static int numopensockets; SYSCTL_INT(_kern_ipc, OID_AUTO, numopensockets, CTLFLAG_RD, &numopensockets, 0, "Number of open sockets"); /* * accept_mtx locks down per-socket fields relating to accept queues. See * socketvar.h for an annotation of the protected fields of struct socket. */ struct mtx accept_mtx; MTX_SYSINIT(accept_mtx, &accept_mtx, "accept", MTX_DEF); /* * so_global_mtx protects so_gencnt, numopensockets, and the per-socket * so_gencnt field. */ static struct mtx so_global_mtx; MTX_SYSINIT(so_global_mtx, &so_global_mtx, "so_glabel", MTX_DEF); /* * General IPC sysctl name space, used by sockets and a variety of other IPC * types. */ SYSCTL_NODE(_kern, KERN_IPC, ipc, CTLFLAG_RW, 0, "IPC"); /* * Initialize the socket subsystem and set up the socket * memory allocator. */ static uma_zone_t socket_zone; int maxsockets; static void socket_zone_change(void *tag) { maxsockets = uma_zone_set_max(socket_zone, maxsockets); } static void socket_hhook_register(int subtype) { if (hhook_head_register(HHOOK_TYPE_SOCKET, subtype, &V_socket_hhh[subtype], HHOOK_NOWAIT|HHOOK_HEADISINVNET) != 0) printf("%s: WARNING: unable to register hook\n", __func__); } static void socket_hhook_deregister(int subtype) { if (hhook_head_deregister(V_socket_hhh[subtype]) != 0) printf("%s: WARNING: unable to deregister hook\n", __func__); } static void socket_init(void *tag) { socket_zone = uma_zcreate("socket", sizeof(struct socket), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_NOFREE); maxsockets = uma_zone_set_max(socket_zone, maxsockets); uma_zone_set_warning(socket_zone, "kern.ipc.maxsockets limit reached"); EVENTHANDLER_REGISTER(maxsockets_change, socket_zone_change, NULL, EVENTHANDLER_PRI_FIRST); } SYSINIT(socket, SI_SUB_PROTO_DOMAININIT, SI_ORDER_ANY, socket_init, NULL); static void socket_vnet_init(const void *unused __unused) { int i; /* We expect a contiguous range */ for (i = 0; i <= HHOOK_SOCKET_LAST; i++) socket_hhook_register(i); } VNET_SYSINIT(socket_vnet_init, SI_SUB_PROTO_DOMAININIT, SI_ORDER_ANY, socket_vnet_init, NULL); static void socket_vnet_uninit(const void *unused __unused) { int i; for (i = 0; i <= HHOOK_SOCKET_LAST; i++) socket_hhook_deregister(i); } VNET_SYSUNINIT(socket_vnet_uninit, SI_SUB_PROTO_DOMAININIT, SI_ORDER_ANY, socket_vnet_uninit, NULL); /* * Initialise maxsockets. This SYSINIT must be run after * tunable_mbinit(). */ static void init_maxsockets(void *ignored) { TUNABLE_INT_FETCH("kern.ipc.maxsockets", &maxsockets); maxsockets = imax(maxsockets, maxfiles); } SYSINIT(param, SI_SUB_TUNABLES, SI_ORDER_ANY, init_maxsockets, NULL); /* * Sysctl to get and set the maximum global sockets limit. Notify protocols * of the change so that they can update their dependent limits as required. */ static int sysctl_maxsockets(SYSCTL_HANDLER_ARGS) { int error, newmaxsockets; newmaxsockets = maxsockets; error = sysctl_handle_int(oidp, &newmaxsockets, 0, req); if (error == 0 && req->newptr) { if (newmaxsockets > maxsockets && newmaxsockets <= maxfiles) { maxsockets = newmaxsockets; EVENTHANDLER_INVOKE(maxsockets_change); } else error = EINVAL; } return (error); } SYSCTL_PROC(_kern_ipc, OID_AUTO, maxsockets, CTLTYPE_INT|CTLFLAG_RW, &maxsockets, 0, sysctl_maxsockets, "IU", "Maximum number of sockets avaliable"); /* * Socket operation routines. These routines are called by the routines in * sys_socket.c or from a system process, and implement the semantics of * socket operations by switching out to the protocol specific routines. */ /* * Get a socket structure from our zone, and initialize it. Note that it * would probably be better to allocate socket and PCB at the same time, but * I'm not convinced that all the protocols can be easily modified to do * this. * * soalloc() returns a socket with a ref count of 0. */ static struct socket * soalloc(struct vnet *vnet) { struct socket *so; so = uma_zalloc(socket_zone, M_NOWAIT | M_ZERO); if (so == NULL) return (NULL); #ifdef MAC if (mac_socket_init(so, M_NOWAIT) != 0) { uma_zfree(socket_zone, so); return (NULL); } #endif if (khelp_init_osd(HELPER_CLASS_SOCKET, &so->osd)) { uma_zfree(socket_zone, so); return (NULL); } SOCKBUF_LOCK_INIT(&so->so_snd, "so_snd"); SOCKBUF_LOCK_INIT(&so->so_rcv, "so_rcv"); sx_init(&so->so_snd.sb_sx, "so_snd_sx"); sx_init(&so->so_rcv.sb_sx, "so_rcv_sx"); TAILQ_INIT(&so->so_aiojobq); #ifdef VIMAGE VNET_ASSERT(vnet != NULL, ("%s:%d vnet is NULL, so=%p", __func__, __LINE__, so)); so->so_vnet = vnet; #endif /* We shouldn't need the so_global_mtx */ if (hhook_run_socket(so, NULL, HHOOK_SOCKET_CREATE)) { /* Do we need more comprehensive error returns? */ uma_zfree(socket_zone, so); return (NULL); } mtx_lock(&so_global_mtx); so->so_gencnt = ++so_gencnt; ++numopensockets; #ifdef VIMAGE vnet->vnet_sockcnt++; #endif mtx_unlock(&so_global_mtx); return (so); } /* * Free the storage associated with a socket at the socket layer, tear down * locks, labels, etc. All protocol state is assumed already to have been * torn down (and possibly never set up) by the caller. */ static void sodealloc(struct socket *so) { KASSERT(so->so_count == 0, ("sodealloc(): so_count %d", so->so_count)); KASSERT(so->so_pcb == NULL, ("sodealloc(): so_pcb != NULL")); mtx_lock(&so_global_mtx); so->so_gencnt = ++so_gencnt; --numopensockets; /* Could be below, but faster here. */ #ifdef VIMAGE VNET_ASSERT(so->so_vnet != NULL, ("%s:%d so_vnet is NULL, so=%p", __func__, __LINE__, so)); so->so_vnet->vnet_sockcnt--; #endif mtx_unlock(&so_global_mtx); if (so->so_rcv.sb_hiwat) (void)chgsbsize(so->so_cred->cr_uidinfo, &so->so_rcv.sb_hiwat, 0, RLIM_INFINITY); if (so->so_snd.sb_hiwat) (void)chgsbsize(so->so_cred->cr_uidinfo, &so->so_snd.sb_hiwat, 0, RLIM_INFINITY); /* remove acccept filter if one is present. */ if (so->so_accf != NULL) do_setopt_accept_filter(so, NULL); #ifdef MAC mac_socket_destroy(so); #endif hhook_run_socket(so, NULL, HHOOK_SOCKET_CLOSE); crfree(so->so_cred); khelp_destroy_osd(&so->osd); sx_destroy(&so->so_snd.sb_sx); sx_destroy(&so->so_rcv.sb_sx); SOCKBUF_LOCK_DESTROY(&so->so_snd); SOCKBUF_LOCK_DESTROY(&so->so_rcv); uma_zfree(socket_zone, so); } /* * socreate returns a socket with a ref count of 1. The socket should be * closed with soclose(). */ int socreate(int dom, struct socket **aso, int type, int proto, struct ucred *cred, struct thread *td) { struct protosw *prp; struct socket *so; int error; if (proto) prp = pffindproto(dom, proto, type); else prp = pffindtype(dom, type); if (prp == NULL) { /* No support for domain. */ if (pffinddomain(dom) == NULL) return (EAFNOSUPPORT); /* No support for socket type. */ if (proto == 0 && type != 0) return (EPROTOTYPE); return (EPROTONOSUPPORT); } if (prp->pr_usrreqs->pru_attach == NULL || prp->pr_usrreqs->pru_attach == pru_attach_notsupp) return (EPROTONOSUPPORT); if (prison_check_af(cred, prp->pr_domain->dom_family) != 0) return (EPROTONOSUPPORT); if (prp->pr_type != type) return (EPROTOTYPE); so = soalloc(CRED_TO_VNET(cred)); if (so == NULL) return (ENOBUFS); TAILQ_INIT(&so->so_incomp); TAILQ_INIT(&so->so_comp); so->so_type = type; so->so_cred = crhold(cred); if ((prp->pr_domain->dom_family == PF_INET) || (prp->pr_domain->dom_family == PF_INET6) || (prp->pr_domain->dom_family == PF_ROUTE)) so->so_fibnum = td->td_proc->p_fibnum; else so->so_fibnum = 0; so->so_proto = prp; #ifdef MAC mac_socket_create(cred, so); #endif knlist_init_mtx(&so->so_rcv.sb_sel.si_note, SOCKBUF_MTX(&so->so_rcv)); knlist_init_mtx(&so->so_snd.sb_sel.si_note, SOCKBUF_MTX(&so->so_snd)); so->so_count = 1; /* * Auto-sizing of socket buffers is managed by the protocols and * the appropriate flags must be set in the pru_attach function. */ CURVNET_SET(so->so_vnet); error = (*prp->pr_usrreqs->pru_attach)(so, proto, td); CURVNET_RESTORE(); if (error) { KASSERT(so->so_count == 1, ("socreate: so_count %d", so->so_count)); so->so_count = 0; sodealloc(so); return (error); } *aso = so; return (0); } #ifdef REGRESSION static int regression_sonewconn_earlytest = 1; SYSCTL_INT(_regression, OID_AUTO, sonewconn_earlytest, CTLFLAG_RW, ®ression_sonewconn_earlytest, 0, "Perform early sonewconn limit test"); #endif /* * When an attempt at a new connection is noted on a socket which accepts * connections, sonewconn is called. If the connection is possible (subject * to space constraints, etc.) then we allocate a new structure, propoerly * linked into the data structure of the original socket, and return this. * Connstatus may be 0, or SS_ISCONFIRMING, or SS_ISCONNECTED. * * Note: the ref count on the socket is 0 on return. */ struct socket * sonewconn(struct socket *head, int connstatus) { static struct timeval lastover; static struct timeval overinterval = { 60, 0 }; static int overcount; struct socket *so; int over; ACCEPT_LOCK(); over = (head->so_qlen > 3 * head->so_qlimit / 2); ACCEPT_UNLOCK(); #ifdef REGRESSION if (regression_sonewconn_earlytest && over) { #else if (over) { #endif overcount++; if (ratecheck(&lastover, &overinterval)) { log(LOG_DEBUG, "%s: pcb %p: Listen queue overflow: " "%i already in queue awaiting acceptance " "(%d occurrences)\n", __func__, head->so_pcb, head->so_qlen, overcount); overcount = 0; } return (NULL); } VNET_ASSERT(head->so_vnet != NULL, ("%s:%d so_vnet is NULL, head=%p", __func__, __LINE__, head)); so = soalloc(head->so_vnet); if (so == NULL) { log(LOG_DEBUG, "%s: pcb %p: New socket allocation failure: " "limit reached or out of memory\n", __func__, head->so_pcb); return (NULL); } if ((head->so_options & SO_ACCEPTFILTER) != 0) connstatus = 0; so->so_head = head; so->so_type = head->so_type; so->so_options = head->so_options &~ SO_ACCEPTCONN; so->so_linger = head->so_linger; so->so_state = head->so_state | SS_NOFDREF; so->so_fibnum = head->so_fibnum; so->so_proto = head->so_proto; so->so_cred = crhold(head->so_cred); #ifdef MAC mac_socket_newconn(head, so); #endif knlist_init_mtx(&so->so_rcv.sb_sel.si_note, SOCKBUF_MTX(&so->so_rcv)); knlist_init_mtx(&so->so_snd.sb_sel.si_note, SOCKBUF_MTX(&so->so_snd)); VNET_SO_ASSERT(head); if (soreserve(so, head->so_snd.sb_hiwat, head->so_rcv.sb_hiwat)) { sodealloc(so); log(LOG_DEBUG, "%s: pcb %p: soreserve() failed\n", __func__, head->so_pcb); return (NULL); } if ((*so->so_proto->pr_usrreqs->pru_attach)(so, 0, NULL)) { sodealloc(so); log(LOG_DEBUG, "%s: pcb %p: pru_attach() failed\n", __func__, head->so_pcb); return (NULL); } so->so_rcv.sb_lowat = head->so_rcv.sb_lowat; so->so_snd.sb_lowat = head->so_snd.sb_lowat; so->so_rcv.sb_timeo = head->so_rcv.sb_timeo; so->so_snd.sb_timeo = head->so_snd.sb_timeo; so->so_rcv.sb_flags |= head->so_rcv.sb_flags & SB_AUTOSIZE; so->so_snd.sb_flags |= head->so_snd.sb_flags & SB_AUTOSIZE; so->so_state |= connstatus; ACCEPT_LOCK(); /* * The accept socket may be tearing down but we just * won a race on the ACCEPT_LOCK. * However, if sctp_peeloff() is called on a 1-to-many * style socket, the SO_ACCEPTCONN doesn't need to be set. */ if (!(head->so_options & SO_ACCEPTCONN) && ((head->so_proto->pr_protocol != IPPROTO_SCTP) || (head->so_type != SOCK_SEQPACKET))) { SOCK_LOCK(so); so->so_head = NULL; sofree(so); /* NB: returns ACCEPT_UNLOCK'ed. */ return (NULL); } if (connstatus) { TAILQ_INSERT_TAIL(&head->so_comp, so, so_list); so->so_qstate |= SQ_COMP; head->so_qlen++; } else { /* * Keep removing sockets from the head until there's room for * us to insert on the tail. In pre-locking revisions, this * was a simple if(), but as we could be racing with other * threads and soabort() requires dropping locks, we must * loop waiting for the condition to be true. */ while (head->so_incqlen > head->so_qlimit) { struct socket *sp; sp = TAILQ_FIRST(&head->so_incomp); TAILQ_REMOVE(&head->so_incomp, sp, so_list); head->so_incqlen--; sp->so_qstate &= ~SQ_INCOMP; sp->so_head = NULL; ACCEPT_UNLOCK(); soabort(sp); ACCEPT_LOCK(); } TAILQ_INSERT_TAIL(&head->so_incomp, so, so_list); so->so_qstate |= SQ_INCOMP; head->so_incqlen++; } ACCEPT_UNLOCK(); if (connstatus) { sorwakeup(head); wakeup_one(&head->so_timeo); } return (so); } int sobind(struct socket *so, struct sockaddr *nam, struct thread *td) { int error; CURVNET_SET(so->so_vnet); error = (*so->so_proto->pr_usrreqs->pru_bind)(so, nam, td); CURVNET_RESTORE(); return (error); } int sobindat(int fd, struct socket *so, struct sockaddr *nam, struct thread *td) { int error; CURVNET_SET(so->so_vnet); error = (*so->so_proto->pr_usrreqs->pru_bindat)(fd, so, nam, td); CURVNET_RESTORE(); return (error); } /* * solisten() transitions a socket from a non-listening state to a listening * state, but can also be used to update the listen queue depth on an * existing listen socket. The protocol will call back into the sockets * layer using solisten_proto_check() and solisten_proto() to check and set * socket-layer listen state. Call backs are used so that the protocol can * acquire both protocol and socket layer locks in whatever order is required * by the protocol. * * Protocol implementors are advised to hold the socket lock across the * socket-layer test and set to avoid races at the socket layer. */ int solisten(struct socket *so, int backlog, struct thread *td) { int error; CURVNET_SET(so->so_vnet); error = (*so->so_proto->pr_usrreqs->pru_listen)(so, backlog, td); CURVNET_RESTORE(); return (error); } int solisten_proto_check(struct socket *so) { SOCK_LOCK_ASSERT(so); if (so->so_state & (SS_ISCONNECTED | SS_ISCONNECTING | SS_ISDISCONNECTING)) return (EINVAL); return (0); } void solisten_proto(struct socket *so, int backlog) { SOCK_LOCK_ASSERT(so); if (backlog < 0 || backlog > somaxconn) backlog = somaxconn; so->so_qlimit = backlog; so->so_options |= SO_ACCEPTCONN; } /* * Evaluate the reference count and named references on a socket; if no * references remain, free it. This should be called whenever a reference is * released, such as in sorele(), but also when named reference flags are * cleared in socket or protocol code. * * sofree() will free the socket if: * * - There are no outstanding file descriptor references or related consumers * (so_count == 0). * * - The socket has been closed by user space, if ever open (SS_NOFDREF). * * - The protocol does not have an outstanding strong reference on the socket * (SS_PROTOREF). * * - The socket is not in a completed connection queue, so a process has been * notified that it is present. If it is removed, the user process may * block in accept() despite select() saying the socket was ready. */ void sofree(struct socket *so) { struct protosw *pr = so->so_proto; struct socket *head; ACCEPT_LOCK_ASSERT(); SOCK_LOCK_ASSERT(so); if ((so->so_state & SS_NOFDREF) == 0 || so->so_count != 0 || (so->so_state & SS_PROTOREF) || (so->so_qstate & SQ_COMP)) { SOCK_UNLOCK(so); ACCEPT_UNLOCK(); return; } head = so->so_head; if (head != NULL) { KASSERT((so->so_qstate & SQ_COMP) != 0 || (so->so_qstate & SQ_INCOMP) != 0, ("sofree: so_head != NULL, but neither SQ_COMP nor " "SQ_INCOMP")); KASSERT((so->so_qstate & SQ_COMP) == 0 || (so->so_qstate & SQ_INCOMP) == 0, ("sofree: so->so_qstate is SQ_COMP and also SQ_INCOMP")); TAILQ_REMOVE(&head->so_incomp, so, so_list); head->so_incqlen--; so->so_qstate &= ~SQ_INCOMP; so->so_head = NULL; } KASSERT((so->so_qstate & SQ_COMP) == 0 && (so->so_qstate & SQ_INCOMP) == 0, ("sofree: so_head == NULL, but still SQ_COMP(%d) or SQ_INCOMP(%d)", so->so_qstate & SQ_COMP, so->so_qstate & SQ_INCOMP)); if (so->so_options & SO_ACCEPTCONN) { KASSERT((TAILQ_EMPTY(&so->so_comp)), ("sofree: so_comp populated")); KASSERT((TAILQ_EMPTY(&so->so_incomp)), ("sofree: so_incomp populated")); } SOCK_UNLOCK(so); ACCEPT_UNLOCK(); VNET_SO_ASSERT(so); if (pr->pr_flags & PR_RIGHTS && pr->pr_domain->dom_dispose != NULL) (*pr->pr_domain->dom_dispose)(so->so_rcv.sb_mb); if (pr->pr_usrreqs->pru_detach != NULL) (*pr->pr_usrreqs->pru_detach)(so); /* * From this point on, we assume that no other references to this * socket exist anywhere else in the stack. Therefore, no locks need * to be acquired or held. * * We used to do a lot of socket buffer and socket locking here, as * well as invoke sorflush() and perform wakeups. The direct call to * dom_dispose() and sbrelease_internal() are an inlining of what was * necessary from sorflush(). * * Notice that the socket buffer and kqueue state are torn down * before calling pru_detach. This means that protocols shold not * assume they can perform socket wakeups, etc, in their detach code. */ sbdestroy(&so->so_snd, so); sbdestroy(&so->so_rcv, so); seldrain(&so->so_snd.sb_sel); seldrain(&so->so_rcv.sb_sel); knlist_destroy(&so->so_rcv.sb_sel.si_note); knlist_destroy(&so->so_snd.sb_sel.si_note); sodealloc(so); } /* * Close a socket on last file table reference removal. Initiate disconnect * if connected. Free socket when disconnect complete. * * This function will sorele() the socket. Note that soclose() may be called * prior to the ref count reaching zero. The actual socket structure will * not be freed until the ref count reaches zero. */ int soclose(struct socket *so) { int error = 0; KASSERT(!(so->so_state & SS_NOFDREF), ("soclose: SS_NOFDREF on enter")); CURVNET_SET(so->so_vnet); funsetown(&so->so_sigio); if (so->so_state & SS_ISCONNECTED) { if ((so->so_state & SS_ISDISCONNECTING) == 0) { error = sodisconnect(so); if (error) { if (error == ENOTCONN) error = 0; goto drop; } } if (so->so_options & SO_LINGER) { if ((so->so_state & SS_ISDISCONNECTING) && (so->so_state & SS_NBIO)) goto drop; while (so->so_state & SS_ISCONNECTED) { error = tsleep(&so->so_timeo, PSOCK | PCATCH, "soclos", so->so_linger * hz); if (error) break; } } } drop: if (so->so_proto->pr_usrreqs->pru_close != NULL) (*so->so_proto->pr_usrreqs->pru_close)(so); ACCEPT_LOCK(); if (so->so_options & SO_ACCEPTCONN) { struct socket *sp; /* * Prevent new additions to the accept queues due * to ACCEPT_LOCK races while we are draining them. */ so->so_options &= ~SO_ACCEPTCONN; while ((sp = TAILQ_FIRST(&so->so_incomp)) != NULL) { TAILQ_REMOVE(&so->so_incomp, sp, so_list); so->so_incqlen--; sp->so_qstate &= ~SQ_INCOMP; sp->so_head = NULL; ACCEPT_UNLOCK(); soabort(sp); ACCEPT_LOCK(); } while ((sp = TAILQ_FIRST(&so->so_comp)) != NULL) { TAILQ_REMOVE(&so->so_comp, sp, so_list); so->so_qlen--; sp->so_qstate &= ~SQ_COMP; sp->so_head = NULL; ACCEPT_UNLOCK(); soabort(sp); ACCEPT_LOCK(); } KASSERT((TAILQ_EMPTY(&so->so_comp)), ("%s: so_comp populated", __func__)); KASSERT((TAILQ_EMPTY(&so->so_incomp)), ("%s: so_incomp populated", __func__)); } SOCK_LOCK(so); KASSERT((so->so_state & SS_NOFDREF) == 0, ("soclose: NOFDREF")); so->so_state |= SS_NOFDREF; sorele(so); /* NB: Returns with ACCEPT_UNLOCK(). */ CURVNET_RESTORE(); return (error); } /* * soabort() is used to abruptly tear down a connection, such as when a * resource limit is reached (listen queue depth exceeded), or if a listen * socket is closed while there are sockets waiting to be accepted. * * This interface is tricky, because it is called on an unreferenced socket, * and must be called only by a thread that has actually removed the socket * from the listen queue it was on, or races with other threads are risked. * * This interface will call into the protocol code, so must not be called * with any socket locks held. Protocols do call it while holding their own * recursible protocol mutexes, but this is something that should be subject * to review in the future. */ void soabort(struct socket *so) { /* * In as much as is possible, assert that no references to this * socket are held. This is not quite the same as asserting that the * current thread is responsible for arranging for no references, but * is as close as we can get for now. */ KASSERT(so->so_count == 0, ("soabort: so_count")); KASSERT((so->so_state & SS_PROTOREF) == 0, ("soabort: SS_PROTOREF")); KASSERT(so->so_state & SS_NOFDREF, ("soabort: !SS_NOFDREF")); KASSERT((so->so_state & SQ_COMP) == 0, ("soabort: SQ_COMP")); KASSERT((so->so_state & SQ_INCOMP) == 0, ("soabort: SQ_INCOMP")); VNET_SO_ASSERT(so); if (so->so_proto->pr_usrreqs->pru_abort != NULL) (*so->so_proto->pr_usrreqs->pru_abort)(so); ACCEPT_LOCK(); SOCK_LOCK(so); sofree(so); } int soaccept(struct socket *so, struct sockaddr **nam) { int error; SOCK_LOCK(so); KASSERT((so->so_state & SS_NOFDREF) != 0, ("soaccept: !NOFDREF")); so->so_state &= ~SS_NOFDREF; SOCK_UNLOCK(so); CURVNET_SET(so->so_vnet); error = (*so->so_proto->pr_usrreqs->pru_accept)(so, nam); CURVNET_RESTORE(); return (error); } int soconnect(struct socket *so, struct sockaddr *nam, struct thread *td) { return (soconnectat(AT_FDCWD, so, nam, td)); } int soconnectat(int fd, struct socket *so, struct sockaddr *nam, struct thread *td) { int error; if (so->so_options & SO_ACCEPTCONN) return (EOPNOTSUPP); CURVNET_SET(so->so_vnet); /* * If protocol is connection-based, can only connect once. * Otherwise, if connected, try to disconnect first. This allows * user to disconnect by connecting to, e.g., a null address. */ if (so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING) && ((so->so_proto->pr_flags & PR_CONNREQUIRED) || (error = sodisconnect(so)))) { error = EISCONN; } else { /* * Prevent accumulated error from previous connection from * biting us. */ so->so_error = 0; if (fd == AT_FDCWD) { error = (*so->so_proto->pr_usrreqs->pru_connect)(so, nam, td); } else { error = (*so->so_proto->pr_usrreqs->pru_connectat)(fd, so, nam, td); } } CURVNET_RESTORE(); return (error); } int soconnect2(struct socket *so1, struct socket *so2) { int error; CURVNET_SET(so1->so_vnet); error = (*so1->so_proto->pr_usrreqs->pru_connect2)(so1, so2); CURVNET_RESTORE(); return (error); } int sodisconnect(struct socket *so) { int error; if ((so->so_state & SS_ISCONNECTED) == 0) return (ENOTCONN); if (so->so_state & SS_ISDISCONNECTING) return (EALREADY); VNET_SO_ASSERT(so); error = (*so->so_proto->pr_usrreqs->pru_disconnect)(so); return (error); } #define SBLOCKWAIT(f) (((f) & MSG_DONTWAIT) ? 0 : SBL_WAIT) int sosend_dgram(struct socket *so, struct sockaddr *addr, struct uio *uio, struct mbuf *top, struct mbuf *control, int flags, struct thread *td) { long space; ssize_t resid; int clen = 0, error, dontroute; KASSERT(so->so_type == SOCK_DGRAM, ("sosend_dgram: !SOCK_DGRAM")); KASSERT(so->so_proto->pr_flags & PR_ATOMIC, ("sosend_dgram: !PR_ATOMIC")); if (uio != NULL) resid = uio->uio_resid; else resid = top->m_pkthdr.len; /* * In theory resid should be unsigned. However, space must be * signed, as it might be less than 0 if we over-committed, and we * must use a signed comparison of space and resid. On the other * hand, a negative resid causes us to loop sending 0-length * segments to the protocol. */ if (resid < 0) { error = EINVAL; goto out; } dontroute = (flags & MSG_DONTROUTE) && (so->so_options & SO_DONTROUTE) == 0; if (td != NULL) td->td_ru.ru_msgsnd++; if (control != NULL) clen = control->m_len; SOCKBUF_LOCK(&so->so_snd); if (so->so_snd.sb_state & SBS_CANTSENDMORE) { SOCKBUF_UNLOCK(&so->so_snd); error = EPIPE; goto out; } if (so->so_error) { error = so->so_error; so->so_error = 0; SOCKBUF_UNLOCK(&so->so_snd); goto out; } if ((so->so_state & SS_ISCONNECTED) == 0) { /* * `sendto' and `sendmsg' is allowed on a connection-based * socket if it supports implied connect. Return ENOTCONN if * not connected and no address is supplied. */ if ((so->so_proto->pr_flags & PR_CONNREQUIRED) && (so->so_proto->pr_flags & PR_IMPLOPCL) == 0) { if ((so->so_state & SS_ISCONFIRMING) == 0 && !(resid == 0 && clen != 0)) { SOCKBUF_UNLOCK(&so->so_snd); error = ENOTCONN; goto out; } } else if (addr == NULL) { if (so->so_proto->pr_flags & PR_CONNREQUIRED) error = ENOTCONN; else error = EDESTADDRREQ; SOCKBUF_UNLOCK(&so->so_snd); goto out; } } /* * Do we need MSG_OOB support in SOCK_DGRAM? Signs here may be a * problem and need fixing. */ space = sbspace(&so->so_snd); if (flags & MSG_OOB) space += 1024; space -= clen; SOCKBUF_UNLOCK(&so->so_snd); if (resid > space) { error = EMSGSIZE; goto out; } if (uio == NULL) { resid = 0; if (flags & MSG_EOR) top->m_flags |= M_EOR; } else { /* * Copy the data from userland into a mbuf chain. * If no data is to be copied in, a single empty mbuf * is returned. */ top = m_uiotombuf(uio, M_WAITOK, space, max_hdr, (M_PKTHDR | ((flags & MSG_EOR) ? M_EOR : 0))); if (top == NULL) { error = EFAULT; /* only possible error */ goto out; } space -= resid - uio->uio_resid; resid = uio->uio_resid; } KASSERT(resid == 0, ("sosend_dgram: resid != 0")); /* * XXXRW: Frobbing SO_DONTROUTE here is even worse without sblock * than with. */ if (dontroute) { SOCK_LOCK(so); so->so_options |= SO_DONTROUTE; SOCK_UNLOCK(so); } /* * XXX all the SBS_CANTSENDMORE checks previously done could be out * of date. We could have recieved a reset packet in an interrupt or * maybe we slept while doing page faults in uiomove() etc. We could * probably recheck again inside the locking protection here, but * there are probably other places that this also happens. We must * rethink this. */ VNET_SO_ASSERT(so); error = (*so->so_proto->pr_usrreqs->pru_send)(so, (flags & MSG_OOB) ? PRUS_OOB : /* * If the user set MSG_EOF, the protocol understands this flag and * nothing left to send then use PRU_SEND_EOF instead of PRU_SEND. */ ((flags & MSG_EOF) && (so->so_proto->pr_flags & PR_IMPLOPCL) && (resid <= 0)) ? PRUS_EOF : /* If there is more to send set PRUS_MORETOCOME */ (resid > 0 && space > 0) ? PRUS_MORETOCOME : 0, top, addr, control, td); if (dontroute) { SOCK_LOCK(so); so->so_options &= ~SO_DONTROUTE; SOCK_UNLOCK(so); } clen = 0; control = NULL; top = NULL; out: if (top != NULL) m_freem(top); if (control != NULL) m_freem(control); return (error); } /* * Send on a socket. If send must go all at once and message is larger than * send buffering, then hard error. Lock against other senders. If must go * all at once and not enough room now, then inform user that this would * block and do nothing. Otherwise, if nonblocking, send as much as * possible. The data to be sent is described by "uio" if nonzero, otherwise * by the mbuf chain "top" (which must be null if uio is not). Data provided * in mbuf chain must be small enough to send all at once. * * Returns nonzero on error, timeout or signal; callers must check for short * counts if EINTR/ERESTART are returned. Data and control buffers are freed * on return. */ int sosend_generic(struct socket *so, struct sockaddr *addr, struct uio *uio, struct mbuf *top, struct mbuf *control, int flags, struct thread *td) { long space; ssize_t resid; int clen = 0, error, dontroute; int atomic = sosendallatonce(so) || top; if (uio != NULL) resid = uio->uio_resid; else resid = top->m_pkthdr.len; /* * In theory resid should be unsigned. However, space must be * signed, as it might be less than 0 if we over-committed, and we * must use a signed comparison of space and resid. On the other * hand, a negative resid causes us to loop sending 0-length * segments to the protocol. * * Also check to make sure that MSG_EOR isn't used on SOCK_STREAM * type sockets since that's an error. */ if (resid < 0 || (so->so_type == SOCK_STREAM && (flags & MSG_EOR))) { error = EINVAL; goto out; } dontroute = (flags & MSG_DONTROUTE) && (so->so_options & SO_DONTROUTE) == 0 && (so->so_proto->pr_flags & PR_ATOMIC); if (td != NULL) td->td_ru.ru_msgsnd++; if (control != NULL) clen = control->m_len; error = sblock(&so->so_snd, SBLOCKWAIT(flags)); if (error) goto out; restart: do { SOCKBUF_LOCK(&so->so_snd); if (so->so_snd.sb_state & SBS_CANTSENDMORE) { SOCKBUF_UNLOCK(&so->so_snd); error = EPIPE; goto release; } if (so->so_error) { error = so->so_error; so->so_error = 0; SOCKBUF_UNLOCK(&so->so_snd); goto release; } if ((so->so_state & SS_ISCONNECTED) == 0) { /* * `sendto' and `sendmsg' is allowed on a connection- * based socket if it supports implied connect. * Return ENOTCONN if not connected and no address is * supplied. */ if ((so->so_proto->pr_flags & PR_CONNREQUIRED) && (so->so_proto->pr_flags & PR_IMPLOPCL) == 0) { if ((so->so_state & SS_ISCONFIRMING) == 0 && !(resid == 0 && clen != 0)) { SOCKBUF_UNLOCK(&so->so_snd); error = ENOTCONN; goto release; } } else if (addr == NULL) { SOCKBUF_UNLOCK(&so->so_snd); if (so->so_proto->pr_flags & PR_CONNREQUIRED) error = ENOTCONN; else error = EDESTADDRREQ; goto release; } } space = sbspace(&so->so_snd); if (flags & MSG_OOB) space += 1024; if ((atomic && resid > so->so_snd.sb_hiwat) || clen > so->so_snd.sb_hiwat) { SOCKBUF_UNLOCK(&so->so_snd); error = EMSGSIZE; goto release; } if (space < resid + clen && (atomic || space < so->so_snd.sb_lowat || space < clen)) { if ((so->so_state & SS_NBIO) || (flags & MSG_NBIO)) { SOCKBUF_UNLOCK(&so->so_snd); error = EWOULDBLOCK; goto release; } error = sbwait(&so->so_snd); SOCKBUF_UNLOCK(&so->so_snd); if (error) goto release; goto restart; } SOCKBUF_UNLOCK(&so->so_snd); space -= clen; do { if (uio == NULL) { resid = 0; if (flags & MSG_EOR) top->m_flags |= M_EOR; - } else { + } else if (resid > 0) { /* * Copy the data from userland into a mbuf * chain. If no data is to be copied in, * a single empty mbuf is returned. */ top = m_uiotombuf(uio, M_WAITOK, space, (atomic ? max_hdr : 0), (atomic ? M_PKTHDR : 0) | ((flags & MSG_EOR) ? M_EOR : 0)); if (top == NULL) { error = EFAULT; /* only possible error */ goto release; } space -= resid - uio->uio_resid; resid = uio->uio_resid; } if (dontroute) { SOCK_LOCK(so); so->so_options |= SO_DONTROUTE; SOCK_UNLOCK(so); } /* * XXX all the SBS_CANTSENDMORE checks previously * done could be out of date. We could have recieved * a reset packet in an interrupt or maybe we slept * while doing page faults in uiomove() etc. We * could probably recheck again inside the locking * protection here, but there are probably other * places that this also happens. We must rethink * this. */ VNET_SO_ASSERT(so); error = (*so->so_proto->pr_usrreqs->pru_send)(so, (flags & MSG_OOB) ? PRUS_OOB : /* * If the user set MSG_EOF, the protocol understands * this flag and nothing left to send then use * PRU_SEND_EOF instead of PRU_SEND. */ ((flags & MSG_EOF) && (so->so_proto->pr_flags & PR_IMPLOPCL) && (resid <= 0)) ? PRUS_EOF : /* If there is more to send set PRUS_MORETOCOME. */ (resid > 0 && space > 0) ? PRUS_MORETOCOME : 0, top, addr, control, td); if (dontroute) { SOCK_LOCK(so); so->so_options &= ~SO_DONTROUTE; SOCK_UNLOCK(so); } clen = 0; control = NULL; top = NULL; if (error) goto release; } while (resid && space > 0); } while (resid); release: sbunlock(&so->so_snd); out: if (top != NULL) m_freem(top); if (control != NULL) m_freem(control); return (error); } int sosend(struct socket *so, struct sockaddr *addr, struct uio *uio, struct mbuf *top, struct mbuf *control, int flags, struct thread *td) { int error; CURVNET_SET(so->so_vnet); error = so->so_proto->pr_usrreqs->pru_sosend(so, addr, uio, top, control, flags, td); CURVNET_RESTORE(); return (error); } /* * The part of soreceive() that implements reading non-inline out-of-band * data from a socket. For more complete comments, see soreceive(), from * which this code originated. * * Note that soreceive_rcvoob(), unlike the remainder of soreceive(), is * unable to return an mbuf chain to the caller. */ static int soreceive_rcvoob(struct socket *so, struct uio *uio, int flags) { struct protosw *pr = so->so_proto; struct mbuf *m; int error; KASSERT(flags & MSG_OOB, ("soreceive_rcvoob: (flags & MSG_OOB) == 0")); VNET_SO_ASSERT(so); m = m_get(M_WAITOK, MT_DATA); error = (*pr->pr_usrreqs->pru_rcvoob)(so, m, flags & MSG_PEEK); if (error) goto bad; do { error = uiomove(mtod(m, void *), (int) min(uio->uio_resid, m->m_len), uio); m = m_free(m); } while (uio->uio_resid && error == 0 && m); bad: if (m != NULL) m_freem(m); return (error); } /* * Following replacement or removal of the first mbuf on the first mbuf chain * of a socket buffer, push necessary state changes back into the socket * buffer so that other consumers see the values consistently. 'nextrecord' * is the callers locally stored value of the original value of * sb->sb_mb->m_nextpkt which must be restored when the lead mbuf changes. * NOTE: 'nextrecord' may be NULL. */ static __inline void sockbuf_pushsync(struct sockbuf *sb, struct mbuf *nextrecord) { SOCKBUF_LOCK_ASSERT(sb); /* * First, update for the new value of nextrecord. If necessary, make * it the first record. */ if (sb->sb_mb != NULL) sb->sb_mb->m_nextpkt = nextrecord; else sb->sb_mb = nextrecord; /* * Now update any dependent socket buffer fields to reflect the new * state. This is an expanded inline of SB_EMPTY_FIXUP(), with the * addition of a second clause that takes care of the case where * sb_mb has been updated, but remains the last record. */ if (sb->sb_mb == NULL) { sb->sb_mbtail = NULL; sb->sb_lastrecord = NULL; } else if (sb->sb_mb->m_nextpkt == NULL) sb->sb_lastrecord = sb->sb_mb; } /* * Implement receive operations on a socket. We depend on the way that * records are added to the sockbuf by sbappend. In particular, each record * (mbufs linked through m_next) must begin with an address if the protocol * so specifies, followed by an optional mbuf or mbufs containing ancillary * data, and then zero or more mbufs of data. In order to allow parallelism * between network receive and copying to user space, as well as avoid * sleeping with a mutex held, we release the socket buffer mutex during the * user space copy. Although the sockbuf is locked, new data may still be * appended, and thus we must maintain consistency of the sockbuf during that * time. * * The caller may receive the data as a single mbuf chain by supplying an * mbuf **mp0 for use in returning the chain. The uio is then used only for * the count in uio_resid. */ int soreceive_generic(struct socket *so, struct sockaddr **psa, struct uio *uio, struct mbuf **mp0, struct mbuf **controlp, int *flagsp) { struct mbuf *m, **mp; int flags, error, offset; ssize_t len; struct protosw *pr = so->so_proto; struct mbuf *nextrecord; int moff, type = 0; ssize_t orig_resid = uio->uio_resid; mp = mp0; if (psa != NULL) *psa = NULL; if (controlp != NULL) *controlp = NULL; if (flagsp != NULL) flags = *flagsp &~ MSG_EOR; else flags = 0; if (flags & MSG_OOB) return (soreceive_rcvoob(so, uio, flags)); if (mp != NULL) *mp = NULL; if ((pr->pr_flags & PR_WANTRCVD) && (so->so_state & SS_ISCONFIRMING) && uio->uio_resid) { VNET_SO_ASSERT(so); (*pr->pr_usrreqs->pru_rcvd)(so, 0); } error = sblock(&so->so_rcv, SBLOCKWAIT(flags)); if (error) return (error); restart: SOCKBUF_LOCK(&so->so_rcv); m = so->so_rcv.sb_mb; /* * If we have less data than requested, block awaiting more (subject * to any timeout) if: * 1. the current count is less than the low water mark, or * 2. MSG_DONTWAIT is not set */ if (m == NULL || (((flags & MSG_DONTWAIT) == 0 && sbavail(&so->so_rcv) < uio->uio_resid) && sbavail(&so->so_rcv) < so->so_rcv.sb_lowat && m->m_nextpkt == NULL && (pr->pr_flags & PR_ATOMIC) == 0)) { KASSERT(m != NULL || !sbavail(&so->so_rcv), ("receive: m == %p sbavail == %u", m, sbavail(&so->so_rcv))); if (so->so_error) { if (m != NULL) goto dontblock; error = so->so_error; if ((flags & MSG_PEEK) == 0) so->so_error = 0; SOCKBUF_UNLOCK(&so->so_rcv); goto release; } SOCKBUF_LOCK_ASSERT(&so->so_rcv); if (so->so_rcv.sb_state & SBS_CANTRCVMORE) { if (m == NULL) { SOCKBUF_UNLOCK(&so->so_rcv); goto release; } else goto dontblock; } for (; m != NULL; m = m->m_next) if (m->m_type == MT_OOBDATA || (m->m_flags & M_EOR)) { m = so->so_rcv.sb_mb; goto dontblock; } if ((so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING)) == 0 && (so->so_proto->pr_flags & PR_CONNREQUIRED)) { SOCKBUF_UNLOCK(&so->so_rcv); error = ENOTCONN; goto release; } if (uio->uio_resid == 0) { SOCKBUF_UNLOCK(&so->so_rcv); goto release; } if ((so->so_state & SS_NBIO) || (flags & (MSG_DONTWAIT|MSG_NBIO))) { SOCKBUF_UNLOCK(&so->so_rcv); error = EWOULDBLOCK; goto release; } SBLASTRECORDCHK(&so->so_rcv); SBLASTMBUFCHK(&so->so_rcv); error = sbwait(&so->so_rcv); SOCKBUF_UNLOCK(&so->so_rcv); if (error) goto release; goto restart; } dontblock: /* * From this point onward, we maintain 'nextrecord' as a cache of the * pointer to the next record in the socket buffer. We must keep the * various socket buffer pointers and local stack versions of the * pointers in sync, pushing out modifications before dropping the * socket buffer mutex, and re-reading them when picking it up. * * Otherwise, we will race with the network stack appending new data * or records onto the socket buffer by using inconsistent/stale * versions of the field, possibly resulting in socket buffer * corruption. * * By holding the high-level sblock(), we prevent simultaneous * readers from pulling off the front of the socket buffer. */ SOCKBUF_LOCK_ASSERT(&so->so_rcv); if (uio->uio_td) uio->uio_td->td_ru.ru_msgrcv++; KASSERT(m == so->so_rcv.sb_mb, ("soreceive: m != so->so_rcv.sb_mb")); SBLASTRECORDCHK(&so->so_rcv); SBLASTMBUFCHK(&so->so_rcv); nextrecord = m->m_nextpkt; if (pr->pr_flags & PR_ADDR) { KASSERT(m->m_type == MT_SONAME, ("m->m_type == %d", m->m_type)); orig_resid = 0; if (psa != NULL) *psa = sodupsockaddr(mtod(m, struct sockaddr *), M_NOWAIT); if (flags & MSG_PEEK) { m = m->m_next; } else { sbfree(&so->so_rcv, m); so->so_rcv.sb_mb = m_free(m); m = so->so_rcv.sb_mb; sockbuf_pushsync(&so->so_rcv, nextrecord); } } /* * Process one or more MT_CONTROL mbufs present before any data mbufs * in the first mbuf chain on the socket buffer. If MSG_PEEK, we * just copy the data; if !MSG_PEEK, we call into the protocol to * perform externalization (or freeing if controlp == NULL). */ if (m != NULL && m->m_type == MT_CONTROL) { struct mbuf *cm = NULL, *cmn; struct mbuf **cme = &cm; do { if (flags & MSG_PEEK) { if (controlp != NULL) { *controlp = m_copy(m, 0, m->m_len); controlp = &(*controlp)->m_next; } m = m->m_next; } else { sbfree(&so->so_rcv, m); so->so_rcv.sb_mb = m->m_next; m->m_next = NULL; *cme = m; cme = &(*cme)->m_next; m = so->so_rcv.sb_mb; } } while (m != NULL && m->m_type == MT_CONTROL); if ((flags & MSG_PEEK) == 0) sockbuf_pushsync(&so->so_rcv, nextrecord); while (cm != NULL) { cmn = cm->m_next; cm->m_next = NULL; if (pr->pr_domain->dom_externalize != NULL) { SOCKBUF_UNLOCK(&so->so_rcv); VNET_SO_ASSERT(so); error = (*pr->pr_domain->dom_externalize) (cm, controlp, flags); SOCKBUF_LOCK(&so->so_rcv); } else if (controlp != NULL) *controlp = cm; else m_freem(cm); if (controlp != NULL) { orig_resid = 0; while (*controlp != NULL) controlp = &(*controlp)->m_next; } cm = cmn; } if (m != NULL) nextrecord = so->so_rcv.sb_mb->m_nextpkt; else nextrecord = so->so_rcv.sb_mb; orig_resid = 0; } if (m != NULL) { if ((flags & MSG_PEEK) == 0) { KASSERT(m->m_nextpkt == nextrecord, ("soreceive: post-control, nextrecord !sync")); if (nextrecord == NULL) { KASSERT(so->so_rcv.sb_mb == m, ("soreceive: post-control, sb_mb!=m")); KASSERT(so->so_rcv.sb_lastrecord == m, ("soreceive: post-control, lastrecord!=m")); } } type = m->m_type; if (type == MT_OOBDATA) flags |= MSG_OOB; } else { if ((flags & MSG_PEEK) == 0) { KASSERT(so->so_rcv.sb_mb == nextrecord, ("soreceive: sb_mb != nextrecord")); if (so->so_rcv.sb_mb == NULL) { KASSERT(so->so_rcv.sb_lastrecord == NULL, ("soreceive: sb_lastercord != NULL")); } } } SOCKBUF_LOCK_ASSERT(&so->so_rcv); SBLASTRECORDCHK(&so->so_rcv); SBLASTMBUFCHK(&so->so_rcv); /* * Now continue to read any data mbufs off of the head of the socket * buffer until the read request is satisfied. Note that 'type' is * used to store the type of any mbuf reads that have happened so far * such that soreceive() can stop reading if the type changes, which * causes soreceive() to return only one of regular data and inline * out-of-band data in a single socket receive operation. */ moff = 0; offset = 0; while (m != NULL && !(m->m_flags & M_NOTAVAIL) && uio->uio_resid > 0 && error == 0) { /* * If the type of mbuf has changed since the last mbuf * examined ('type'), end the receive operation. */ SOCKBUF_LOCK_ASSERT(&so->so_rcv); if (m->m_type == MT_OOBDATA || m->m_type == MT_CONTROL) { if (type != m->m_type) break; } else if (type == MT_OOBDATA) break; else KASSERT(m->m_type == MT_DATA, ("m->m_type == %d", m->m_type)); so->so_rcv.sb_state &= ~SBS_RCVATMARK; len = uio->uio_resid; if (so->so_oobmark && len > so->so_oobmark - offset) len = so->so_oobmark - offset; if (len > m->m_len - moff) len = m->m_len - moff; /* * If mp is set, just pass back the mbufs. Otherwise copy * them out via the uio, then free. Sockbuf must be * consistent here (points to current mbuf, it points to next * record) when we drop priority; we must note any additions * to the sockbuf when we block interrupts again. */ if (mp == NULL) { SOCKBUF_LOCK_ASSERT(&so->so_rcv); SBLASTRECORDCHK(&so->so_rcv); SBLASTMBUFCHK(&so->so_rcv); SOCKBUF_UNLOCK(&so->so_rcv); error = uiomove(mtod(m, char *) + moff, (int)len, uio); SOCKBUF_LOCK(&so->so_rcv); if (error) { /* * The MT_SONAME mbuf has already been removed * from the record, so it is necessary to * remove the data mbufs, if any, to preserve * the invariant in the case of PR_ADDR that * requires MT_SONAME mbufs at the head of * each record. */ if (m && pr->pr_flags & PR_ATOMIC && ((flags & MSG_PEEK) == 0)) (void)sbdroprecord_locked(&so->so_rcv); SOCKBUF_UNLOCK(&so->so_rcv); goto release; } } else uio->uio_resid -= len; SOCKBUF_LOCK_ASSERT(&so->so_rcv); if (len == m->m_len - moff) { if (m->m_flags & M_EOR) flags |= MSG_EOR; if (flags & MSG_PEEK) { m = m->m_next; moff = 0; } else { nextrecord = m->m_nextpkt; sbfree(&so->so_rcv, m); if (mp != NULL) { m->m_nextpkt = NULL; *mp = m; mp = &m->m_next; so->so_rcv.sb_mb = m = m->m_next; *mp = NULL; } else { so->so_rcv.sb_mb = m_free(m); m = so->so_rcv.sb_mb; } sockbuf_pushsync(&so->so_rcv, nextrecord); SBLASTRECORDCHK(&so->so_rcv); SBLASTMBUFCHK(&so->so_rcv); } } else { if (flags & MSG_PEEK) moff += len; else { if (mp != NULL) { if (flags & MSG_DONTWAIT) { *mp = m_copym(m, 0, len, M_NOWAIT); if (*mp == NULL) { /* * m_copym() couldn't * allocate an mbuf. * Adjust uio_resid back * (it was adjusted * down by len bytes, * which we didn't end * up "copying" over). */ uio->uio_resid += len; break; } } else { SOCKBUF_UNLOCK(&so->so_rcv); *mp = m_copym(m, 0, len, M_WAITOK); SOCKBUF_LOCK(&so->so_rcv); } } sbcut_locked(&so->so_rcv, len); } } SOCKBUF_LOCK_ASSERT(&so->so_rcv); if (so->so_oobmark) { if ((flags & MSG_PEEK) == 0) { so->so_oobmark -= len; if (so->so_oobmark == 0) { so->so_rcv.sb_state |= SBS_RCVATMARK; break; } } else { offset += len; if (offset == so->so_oobmark) break; } } if (flags & MSG_EOR) break; /* * If the MSG_WAITALL flag is set (for non-atomic socket), we * must not quit until "uio->uio_resid == 0" or an error * termination. If a signal/timeout occurs, return with a * short count but without error. Keep sockbuf locked * against other readers. */ while (flags & MSG_WAITALL && m == NULL && uio->uio_resid > 0 && !sosendallatonce(so) && nextrecord == NULL) { SOCKBUF_LOCK_ASSERT(&so->so_rcv); if (so->so_error || so->so_rcv.sb_state & SBS_CANTRCVMORE) break; /* * Notify the protocol that some data has been * drained before blocking. */ if (pr->pr_flags & PR_WANTRCVD) { SOCKBUF_UNLOCK(&so->so_rcv); VNET_SO_ASSERT(so); (*pr->pr_usrreqs->pru_rcvd)(so, flags); SOCKBUF_LOCK(&so->so_rcv); } SBLASTRECORDCHK(&so->so_rcv); SBLASTMBUFCHK(&so->so_rcv); /* * We could receive some data while was notifying * the protocol. Skip blocking in this case. */ if (so->so_rcv.sb_mb == NULL) { error = sbwait(&so->so_rcv); if (error) { SOCKBUF_UNLOCK(&so->so_rcv); goto release; } } m = so->so_rcv.sb_mb; if (m != NULL) nextrecord = m->m_nextpkt; } } SOCKBUF_LOCK_ASSERT(&so->so_rcv); if (m != NULL && pr->pr_flags & PR_ATOMIC) { flags |= MSG_TRUNC; if ((flags & MSG_PEEK) == 0) (void) sbdroprecord_locked(&so->so_rcv); } if ((flags & MSG_PEEK) == 0) { if (m == NULL) { /* * First part is an inline SB_EMPTY_FIXUP(). Second * part makes sure sb_lastrecord is up-to-date if * there is still data in the socket buffer. */ so->so_rcv.sb_mb = nextrecord; if (so->so_rcv.sb_mb == NULL) { so->so_rcv.sb_mbtail = NULL; so->so_rcv.sb_lastrecord = NULL; } else if (nextrecord->m_nextpkt == NULL) so->so_rcv.sb_lastrecord = nextrecord; } SBLASTRECORDCHK(&so->so_rcv); SBLASTMBUFCHK(&so->so_rcv); /* * If soreceive() is being done from the socket callback, * then don't need to generate ACK to peer to update window, * since ACK will be generated on return to TCP. */ if (!(flags & MSG_SOCALLBCK) && (pr->pr_flags & PR_WANTRCVD)) { SOCKBUF_UNLOCK(&so->so_rcv); VNET_SO_ASSERT(so); (*pr->pr_usrreqs->pru_rcvd)(so, flags); SOCKBUF_LOCK(&so->so_rcv); } } SOCKBUF_LOCK_ASSERT(&so->so_rcv); if (orig_resid == uio->uio_resid && orig_resid && (flags & MSG_EOR) == 0 && (so->so_rcv.sb_state & SBS_CANTRCVMORE) == 0) { SOCKBUF_UNLOCK(&so->so_rcv); goto restart; } SOCKBUF_UNLOCK(&so->so_rcv); if (flagsp != NULL) *flagsp |= flags; release: sbunlock(&so->so_rcv); return (error); } /* * Optimized version of soreceive() for stream (TCP) sockets. * XXXAO: (MSG_WAITALL | MSG_PEEK) isn't properly handled. */ int soreceive_stream(struct socket *so, struct sockaddr **psa, struct uio *uio, struct mbuf **mp0, struct mbuf **controlp, int *flagsp) { int len = 0, error = 0, flags, oresid; struct sockbuf *sb; struct mbuf *m, *n = NULL; /* We only do stream sockets. */ if (so->so_type != SOCK_STREAM) return (EINVAL); if (psa != NULL) *psa = NULL; if (controlp != NULL) return (EINVAL); if (flagsp != NULL) flags = *flagsp &~ MSG_EOR; else flags = 0; if (flags & MSG_OOB) return (soreceive_rcvoob(so, uio, flags)); if (mp0 != NULL) *mp0 = NULL; sb = &so->so_rcv; /* Prevent other readers from entering the socket. */ error = sblock(sb, SBLOCKWAIT(flags)); if (error) goto out; SOCKBUF_LOCK(sb); /* Easy one, no space to copyout anything. */ if (uio->uio_resid == 0) { error = EINVAL; goto out; } oresid = uio->uio_resid; /* We will never ever get anything unless we are or were connected. */ if (!(so->so_state & (SS_ISCONNECTED|SS_ISDISCONNECTED))) { error = ENOTCONN; goto out; } restart: SOCKBUF_LOCK_ASSERT(&so->so_rcv); /* Abort if socket has reported problems. */ if (so->so_error) { if (sbavail(sb) > 0) goto deliver; if (oresid > uio->uio_resid) goto out; error = so->so_error; if (!(flags & MSG_PEEK)) so->so_error = 0; goto out; } /* Door is closed. Deliver what is left, if any. */ if (sb->sb_state & SBS_CANTRCVMORE) { if (sbavail(sb) > 0) goto deliver; else goto out; } /* Socket buffer is empty and we shall not block. */ if (sbavail(sb) == 0 && ((so->so_state & SS_NBIO) || (flags & (MSG_DONTWAIT|MSG_NBIO)))) { error = EAGAIN; goto out; } /* Socket buffer got some data that we shall deliver now. */ if (sbavail(sb) > 0 && !(flags & MSG_WAITALL) && ((sb->sb_flags & SS_NBIO) || (flags & (MSG_DONTWAIT|MSG_NBIO)) || sbavail(sb) >= sb->sb_lowat || sbavail(sb) >= uio->uio_resid || sbavail(sb) >= sb->sb_hiwat) ) { goto deliver; } /* On MSG_WAITALL we must wait until all data or error arrives. */ if ((flags & MSG_WAITALL) && (sbavail(sb) >= uio->uio_resid || sbavail(sb) >= sb->sb_hiwat)) goto deliver; /* * Wait and block until (more) data comes in. * NB: Drops the sockbuf lock during wait. */ error = sbwait(sb); if (error) goto out; goto restart; deliver: SOCKBUF_LOCK_ASSERT(&so->so_rcv); KASSERT(sbavail(sb) > 0, ("%s: sockbuf empty", __func__)); KASSERT(sb->sb_mb != NULL, ("%s: sb_mb == NULL", __func__)); /* Statistics. */ if (uio->uio_td) uio->uio_td->td_ru.ru_msgrcv++; /* Fill uio until full or current end of socket buffer is reached. */ len = min(uio->uio_resid, sbavail(sb)); if (mp0 != NULL) { /* Dequeue as many mbufs as possible. */ if (!(flags & MSG_PEEK) && len >= sb->sb_mb->m_len) { if (*mp0 == NULL) *mp0 = sb->sb_mb; else m_cat(*mp0, sb->sb_mb); for (m = sb->sb_mb; m != NULL && m->m_len <= len; m = m->m_next) { KASSERT(!(m->m_flags & M_NOTAVAIL), ("%s: m %p not available", __func__, m)); len -= m->m_len; uio->uio_resid -= m->m_len; sbfree(sb, m); n = m; } n->m_next = NULL; sb->sb_mb = m; sb->sb_lastrecord = sb->sb_mb; if (sb->sb_mb == NULL) SB_EMPTY_FIXUP(sb); } /* Copy the remainder. */ if (len > 0) { KASSERT(sb->sb_mb != NULL, ("%s: len > 0 && sb->sb_mb empty", __func__)); m = m_copym(sb->sb_mb, 0, len, M_NOWAIT); if (m == NULL) len = 0; /* Don't flush data from sockbuf. */ else uio->uio_resid -= len; if (*mp0 != NULL) m_cat(*mp0, m); else *mp0 = m; if (*mp0 == NULL) { error = ENOBUFS; goto out; } } } else { /* NB: Must unlock socket buffer as uiomove may sleep. */ SOCKBUF_UNLOCK(sb); error = m_mbuftouio(uio, sb->sb_mb, len); SOCKBUF_LOCK(sb); if (error) goto out; } SBLASTRECORDCHK(sb); SBLASTMBUFCHK(sb); /* * Remove the delivered data from the socket buffer unless we * were only peeking. */ if (!(flags & MSG_PEEK)) { if (len > 0) sbdrop_locked(sb, len); /* Notify protocol that we drained some data. */ if ((so->so_proto->pr_flags & PR_WANTRCVD) && (((flags & MSG_WAITALL) && uio->uio_resid > 0) || !(flags & MSG_SOCALLBCK))) { SOCKBUF_UNLOCK(sb); VNET_SO_ASSERT(so); (*so->so_proto->pr_usrreqs->pru_rcvd)(so, flags); SOCKBUF_LOCK(sb); } } /* * For MSG_WAITALL we may have to loop again and wait for * more data to come in. */ if ((flags & MSG_WAITALL) && uio->uio_resid > 0) goto restart; out: SOCKBUF_LOCK_ASSERT(sb); SBLASTRECORDCHK(sb); SBLASTMBUFCHK(sb); SOCKBUF_UNLOCK(sb); sbunlock(sb); return (error); } /* * Optimized version of soreceive() for simple datagram cases from userspace. * Unlike in the stream case, we're able to drop a datagram if copyout() * fails, and because we handle datagrams atomically, we don't need to use a * sleep lock to prevent I/O interlacing. */ int soreceive_dgram(struct socket *so, struct sockaddr **psa, struct uio *uio, struct mbuf **mp0, struct mbuf **controlp, int *flagsp) { struct mbuf *m, *m2; int flags, error; ssize_t len; struct protosw *pr = so->so_proto; struct mbuf *nextrecord; if (psa != NULL) *psa = NULL; if (controlp != NULL) *controlp = NULL; if (flagsp != NULL) flags = *flagsp &~ MSG_EOR; else flags = 0; /* * For any complicated cases, fall back to the full * soreceive_generic(). */ if (mp0 != NULL || (flags & MSG_PEEK) || (flags & MSG_OOB)) return (soreceive_generic(so, psa, uio, mp0, controlp, flagsp)); /* * Enforce restrictions on use. */ KASSERT((pr->pr_flags & PR_WANTRCVD) == 0, ("soreceive_dgram: wantrcvd")); KASSERT(pr->pr_flags & PR_ATOMIC, ("soreceive_dgram: !atomic")); KASSERT((so->so_rcv.sb_state & SBS_RCVATMARK) == 0, ("soreceive_dgram: SBS_RCVATMARK")); KASSERT((so->so_proto->pr_flags & PR_CONNREQUIRED) == 0, ("soreceive_dgram: P_CONNREQUIRED")); /* * Loop blocking while waiting for a datagram. */ SOCKBUF_LOCK(&so->so_rcv); while ((m = so->so_rcv.sb_mb) == NULL) { KASSERT(sbavail(&so->so_rcv) == 0, ("soreceive_dgram: sb_mb NULL but sbavail %u", sbavail(&so->so_rcv))); if (so->so_error) { error = so->so_error; so->so_error = 0; SOCKBUF_UNLOCK(&so->so_rcv); return (error); } if (so->so_rcv.sb_state & SBS_CANTRCVMORE || uio->uio_resid == 0) { SOCKBUF_UNLOCK(&so->so_rcv); return (0); } if ((so->so_state & SS_NBIO) || (flags & (MSG_DONTWAIT|MSG_NBIO))) { SOCKBUF_UNLOCK(&so->so_rcv); return (EWOULDBLOCK); } SBLASTRECORDCHK(&so->so_rcv); SBLASTMBUFCHK(&so->so_rcv); error = sbwait(&so->so_rcv); if (error) { SOCKBUF_UNLOCK(&so->so_rcv); return (error); } } SOCKBUF_LOCK_ASSERT(&so->so_rcv); if (uio->uio_td) uio->uio_td->td_ru.ru_msgrcv++; SBLASTRECORDCHK(&so->so_rcv); SBLASTMBUFCHK(&so->so_rcv); nextrecord = m->m_nextpkt; if (nextrecord == NULL) { KASSERT(so->so_rcv.sb_lastrecord == m, ("soreceive_dgram: lastrecord != m")); } KASSERT(so->so_rcv.sb_mb->m_nextpkt == nextrecord, ("soreceive_dgram: m_nextpkt != nextrecord")); /* * Pull 'm' and its chain off the front of the packet queue. */ so->so_rcv.sb_mb = NULL; sockbuf_pushsync(&so->so_rcv, nextrecord); /* * Walk 'm's chain and free that many bytes from the socket buffer. */ for (m2 = m; m2 != NULL; m2 = m2->m_next) sbfree(&so->so_rcv, m2); /* * Do a few last checks before we let go of the lock. */ SBLASTRECORDCHK(&so->so_rcv); SBLASTMBUFCHK(&so->so_rcv); SOCKBUF_UNLOCK(&so->so_rcv); if (pr->pr_flags & PR_ADDR) { KASSERT(m->m_type == MT_SONAME, ("m->m_type == %d", m->m_type)); if (psa != NULL) *psa = sodupsockaddr(mtod(m, struct sockaddr *), M_NOWAIT); m = m_free(m); } if (m == NULL) { /* XXXRW: Can this happen? */ return (0); } /* * Packet to copyout() is now in 'm' and it is disconnected from the * queue. * * Process one or more MT_CONTROL mbufs present before any data mbufs * in the first mbuf chain on the socket buffer. We call into the * protocol to perform externalization (or freeing if controlp == * NULL). */ if (m->m_type == MT_CONTROL) { struct mbuf *cm = NULL, *cmn; struct mbuf **cme = &cm; do { m2 = m->m_next; m->m_next = NULL; *cme = m; cme = &(*cme)->m_next; m = m2; } while (m != NULL && m->m_type == MT_CONTROL); while (cm != NULL) { cmn = cm->m_next; cm->m_next = NULL; if (pr->pr_domain->dom_externalize != NULL) { error = (*pr->pr_domain->dom_externalize) (cm, controlp, flags); } else if (controlp != NULL) *controlp = cm; else m_freem(cm); if (controlp != NULL) { while (*controlp != NULL) controlp = &(*controlp)->m_next; } cm = cmn; } } KASSERT(m->m_type == MT_DATA, ("soreceive_dgram: !data")); while (m != NULL && uio->uio_resid > 0) { len = uio->uio_resid; if (len > m->m_len) len = m->m_len; error = uiomove(mtod(m, char *), (int)len, uio); if (error) { m_freem(m); return (error); } if (len == m->m_len) m = m_free(m); else { m->m_data += len; m->m_len -= len; } } if (m != NULL) flags |= MSG_TRUNC; m_freem(m); if (flagsp != NULL) *flagsp |= flags; return (0); } int soreceive(struct socket *so, struct sockaddr **psa, struct uio *uio, struct mbuf **mp0, struct mbuf **controlp, int *flagsp) { int error; CURVNET_SET(so->so_vnet); error = (so->so_proto->pr_usrreqs->pru_soreceive(so, psa, uio, mp0, controlp, flagsp)); CURVNET_RESTORE(); return (error); } int soshutdown(struct socket *so, int how) { struct protosw *pr = so->so_proto; int error; if (!(how == SHUT_RD || how == SHUT_WR || how == SHUT_RDWR)) return (EINVAL); CURVNET_SET(so->so_vnet); if (pr->pr_usrreqs->pru_flush != NULL) (*pr->pr_usrreqs->pru_flush)(so, how); if (how != SHUT_WR) sorflush(so); if (how != SHUT_RD) { error = (*pr->pr_usrreqs->pru_shutdown)(so); wakeup(&so->so_timeo); CURVNET_RESTORE(); return (error); } wakeup(&so->so_timeo); CURVNET_RESTORE(); return (0); } void sorflush(struct socket *so) { struct sockbuf *sb = &so->so_rcv; struct protosw *pr = so->so_proto; struct sockbuf asb; VNET_SO_ASSERT(so); /* * In order to avoid calling dom_dispose with the socket buffer mutex * held, and in order to generally avoid holding the lock for a long * time, we make a copy of the socket buffer and clear the original * (except locks, state). The new socket buffer copy won't have * initialized locks so we can only call routines that won't use or * assert those locks. * * Dislodge threads currently blocked in receive and wait to acquire * a lock against other simultaneous readers before clearing the * socket buffer. Don't let our acquire be interrupted by a signal * despite any existing socket disposition on interruptable waiting. */ socantrcvmore(so); (void) sblock(sb, SBL_WAIT | SBL_NOINTR); /* * Invalidate/clear most of the sockbuf structure, but leave selinfo * and mutex data unchanged. */ SOCKBUF_LOCK(sb); bzero(&asb, offsetof(struct sockbuf, sb_startzero)); bcopy(&sb->sb_startzero, &asb.sb_startzero, sizeof(*sb) - offsetof(struct sockbuf, sb_startzero)); bzero(&sb->sb_startzero, sizeof(*sb) - offsetof(struct sockbuf, sb_startzero)); SOCKBUF_UNLOCK(sb); sbunlock(sb); /* * Dispose of special rights and flush the socket buffer. Don't call * any unsafe routines (that rely on locks being initialized) on asb. */ if (pr->pr_flags & PR_RIGHTS && pr->pr_domain->dom_dispose != NULL) (*pr->pr_domain->dom_dispose)(asb.sb_mb); sbrelease_internal(&asb, so); } /* * Wrapper for Socket established helper hook. * Parameters: socket, context of the hook point, hook id. */ static int inline hhook_run_socket(struct socket *so, void *hctx, int32_t h_id) { struct socket_hhook_data hhook_data = { .so = so, .hctx = hctx, .m = NULL, .status = 0 }; CURVNET_SET(so->so_vnet); HHOOKS_RUN_IF(V_socket_hhh[h_id], &hhook_data, &so->osd); CURVNET_RESTORE(); /* Ugly but needed, since hhooks return void for now */ return (hhook_data.status); } /* * Perhaps this routine, and sooptcopyout(), below, ought to come in an * additional variant to handle the case where the option value needs to be * some kind of integer, but not a specific size. In addition to their use * here, these functions are also called by the protocol-level pr_ctloutput() * routines. */ int sooptcopyin(struct sockopt *sopt, void *buf, size_t len, size_t minlen) { size_t valsize; /* * If the user gives us more than we wanted, we ignore it, but if we * don't get the minimum length the caller wants, we return EINVAL. * On success, sopt->sopt_valsize is set to however much we actually * retrieved. */ if ((valsize = sopt->sopt_valsize) < minlen) return EINVAL; if (valsize > len) sopt->sopt_valsize = valsize = len; if (sopt->sopt_td != NULL) return (copyin(sopt->sopt_val, buf, valsize)); bcopy(sopt->sopt_val, buf, valsize); return (0); } /* * Kernel version of setsockopt(2). * * XXX: optlen is size_t, not socklen_t */ int so_setsockopt(struct socket *so, int level, int optname, void *optval, size_t optlen) { struct sockopt sopt; sopt.sopt_level = level; sopt.sopt_name = optname; sopt.sopt_dir = SOPT_SET; sopt.sopt_val = optval; sopt.sopt_valsize = optlen; sopt.sopt_td = NULL; return (sosetopt(so, &sopt)); } int sosetopt(struct socket *so, struct sockopt *sopt) { int error, optval; struct linger l; struct timeval tv; sbintime_t val; uint32_t val32; #ifdef MAC struct mac extmac; #endif CURVNET_SET(so->so_vnet); error = 0; if (sopt->sopt_level != SOL_SOCKET) { if (so->so_proto->pr_ctloutput != NULL) { error = (*so->so_proto->pr_ctloutput)(so, sopt); CURVNET_RESTORE(); return (error); } error = ENOPROTOOPT; } else { switch (sopt->sopt_name) { case SO_ACCEPTFILTER: error = do_setopt_accept_filter(so, sopt); if (error) goto bad; break; case SO_LINGER: error = sooptcopyin(sopt, &l, sizeof l, sizeof l); if (error) goto bad; SOCK_LOCK(so); so->so_linger = l.l_linger; if (l.l_onoff) so->so_options |= SO_LINGER; else so->so_options &= ~SO_LINGER; SOCK_UNLOCK(so); break; case SO_DEBUG: case SO_KEEPALIVE: case SO_DONTROUTE: case SO_USELOOPBACK: case SO_BROADCAST: case SO_REUSEADDR: case SO_REUSEPORT: case SO_OOBINLINE: case SO_TIMESTAMP: case SO_BINTIME: case SO_NOSIGPIPE: case SO_NO_DDP: case SO_NO_OFFLOAD: error = sooptcopyin(sopt, &optval, sizeof optval, sizeof optval); if (error) goto bad; SOCK_LOCK(so); if (optval) so->so_options |= sopt->sopt_name; else so->so_options &= ~sopt->sopt_name; SOCK_UNLOCK(so); break; case SO_SETFIB: error = sooptcopyin(sopt, &optval, sizeof optval, sizeof optval); if (error) goto bad; if (optval < 0 || optval >= rt_numfibs) { error = EINVAL; goto bad; } if (((so->so_proto->pr_domain->dom_family == PF_INET) || (so->so_proto->pr_domain->dom_family == PF_INET6) || (so->so_proto->pr_domain->dom_family == PF_ROUTE))) so->so_fibnum = optval; else so->so_fibnum = 0; break; case SO_USER_COOKIE: error = sooptcopyin(sopt, &val32, sizeof val32, sizeof val32); if (error) goto bad; so->so_user_cookie = val32; break; case SO_SNDBUF: case SO_RCVBUF: case SO_SNDLOWAT: case SO_RCVLOWAT: error = sooptcopyin(sopt, &optval, sizeof optval, sizeof optval); if (error) goto bad; /* * Values < 1 make no sense for any of these options, * so disallow them. */ if (optval < 1) { error = EINVAL; goto bad; } switch (sopt->sopt_name) { case SO_SNDBUF: case SO_RCVBUF: if (sbreserve(sopt->sopt_name == SO_SNDBUF ? &so->so_snd : &so->so_rcv, (u_long)optval, so, curthread) == 0) { error = ENOBUFS; goto bad; } (sopt->sopt_name == SO_SNDBUF ? &so->so_snd : &so->so_rcv)->sb_flags &= ~SB_AUTOSIZE; break; /* * Make sure the low-water is never greater than the * high-water. */ case SO_SNDLOWAT: SOCKBUF_LOCK(&so->so_snd); so->so_snd.sb_lowat = (optval > so->so_snd.sb_hiwat) ? so->so_snd.sb_hiwat : optval; SOCKBUF_UNLOCK(&so->so_snd); break; case SO_RCVLOWAT: SOCKBUF_LOCK(&so->so_rcv); so->so_rcv.sb_lowat = (optval > so->so_rcv.sb_hiwat) ? so->so_rcv.sb_hiwat : optval; SOCKBUF_UNLOCK(&so->so_rcv); break; } break; case SO_SNDTIMEO: case SO_RCVTIMEO: #ifdef COMPAT_FREEBSD32 if (SV_CURPROC_FLAG(SV_ILP32)) { struct timeval32 tv32; error = sooptcopyin(sopt, &tv32, sizeof tv32, sizeof tv32); CP(tv32, tv, tv_sec); CP(tv32, tv, tv_usec); } else #endif error = sooptcopyin(sopt, &tv, sizeof tv, sizeof tv); if (error) goto bad; if (tv.tv_sec < 0 || tv.tv_usec < 0 || tv.tv_usec >= 1000000) { error = EDOM; goto bad; } if (tv.tv_sec > INT32_MAX) val = SBT_MAX; else val = tvtosbt(tv); switch (sopt->sopt_name) { case SO_SNDTIMEO: so->so_snd.sb_timeo = val; break; case SO_RCVTIMEO: so->so_rcv.sb_timeo = val; break; } break; case SO_LABEL: #ifdef MAC error = sooptcopyin(sopt, &extmac, sizeof extmac, sizeof extmac); if (error) goto bad; error = mac_setsockopt_label(sopt->sopt_td->td_ucred, so, &extmac); #else error = EOPNOTSUPP; #endif break; default: if (V_socket_hhh[HHOOK_SOCKET_OPT]->hhh_nhooks > 0) error = hhook_run_socket(so, sopt, HHOOK_SOCKET_OPT); else error = ENOPROTOOPT; break; } if (error == 0 && so->so_proto->pr_ctloutput != NULL) (void)(*so->so_proto->pr_ctloutput)(so, sopt); } bad: CURVNET_RESTORE(); return (error); } /* * Helper routine for getsockopt. */ int sooptcopyout(struct sockopt *sopt, const void *buf, size_t len) { int error; size_t valsize; error = 0; /* * Documented get behavior is that we always return a value, possibly * truncated to fit in the user's buffer. Traditional behavior is * that we always tell the user precisely how much we copied, rather * than something useful like the total amount we had available for * her. Note that this interface is not idempotent; the entire * answer must generated ahead of time. */ valsize = min(len, sopt->sopt_valsize); sopt->sopt_valsize = valsize; if (sopt->sopt_val != NULL) { if (sopt->sopt_td != NULL) error = copyout(buf, sopt->sopt_val, valsize); else bcopy(buf, sopt->sopt_val, valsize); } return (error); } int sogetopt(struct socket *so, struct sockopt *sopt) { int error, optval; struct linger l; struct timeval tv; #ifdef MAC struct mac extmac; #endif CURVNET_SET(so->so_vnet); error = 0; if (sopt->sopt_level != SOL_SOCKET) { if (so->so_proto->pr_ctloutput != NULL) error = (*so->so_proto->pr_ctloutput)(so, sopt); else error = ENOPROTOOPT; CURVNET_RESTORE(); return (error); } else { switch (sopt->sopt_name) { case SO_ACCEPTFILTER: error = do_getopt_accept_filter(so, sopt); break; case SO_LINGER: SOCK_LOCK(so); l.l_onoff = so->so_options & SO_LINGER; l.l_linger = so->so_linger; SOCK_UNLOCK(so); error = sooptcopyout(sopt, &l, sizeof l); break; case SO_USELOOPBACK: case SO_DONTROUTE: case SO_DEBUG: case SO_KEEPALIVE: case SO_REUSEADDR: case SO_REUSEPORT: case SO_BROADCAST: case SO_OOBINLINE: case SO_ACCEPTCONN: case SO_TIMESTAMP: case SO_BINTIME: case SO_NOSIGPIPE: optval = so->so_options & sopt->sopt_name; integer: error = sooptcopyout(sopt, &optval, sizeof optval); break; case SO_TYPE: optval = so->so_type; goto integer; case SO_PROTOCOL: optval = so->so_proto->pr_protocol; goto integer; case SO_ERROR: SOCK_LOCK(so); optval = so->so_error; so->so_error = 0; SOCK_UNLOCK(so); goto integer; case SO_SNDBUF: optval = so->so_snd.sb_hiwat; goto integer; case SO_RCVBUF: optval = so->so_rcv.sb_hiwat; goto integer; case SO_SNDLOWAT: optval = so->so_snd.sb_lowat; goto integer; case SO_RCVLOWAT: optval = so->so_rcv.sb_lowat; goto integer; case SO_SNDTIMEO: case SO_RCVTIMEO: tv = sbttotv(sopt->sopt_name == SO_SNDTIMEO ? so->so_snd.sb_timeo : so->so_rcv.sb_timeo); #ifdef COMPAT_FREEBSD32 if (SV_CURPROC_FLAG(SV_ILP32)) { struct timeval32 tv32; CP(tv, tv32, tv_sec); CP(tv, tv32, tv_usec); error = sooptcopyout(sopt, &tv32, sizeof tv32); } else #endif error = sooptcopyout(sopt, &tv, sizeof tv); break; case SO_LABEL: #ifdef MAC error = sooptcopyin(sopt, &extmac, sizeof(extmac), sizeof(extmac)); if (error) goto bad; error = mac_getsockopt_label(sopt->sopt_td->td_ucred, so, &extmac); if (error) goto bad; error = sooptcopyout(sopt, &extmac, sizeof extmac); #else error = EOPNOTSUPP; #endif break; case SO_PEERLABEL: #ifdef MAC error = sooptcopyin(sopt, &extmac, sizeof(extmac), sizeof(extmac)); if (error) goto bad; error = mac_getsockopt_peerlabel( sopt->sopt_td->td_ucred, so, &extmac); if (error) goto bad; error = sooptcopyout(sopt, &extmac, sizeof extmac); #else error = EOPNOTSUPP; #endif break; case SO_LISTENQLIMIT: optval = so->so_qlimit; goto integer; case SO_LISTENQLEN: optval = so->so_qlen; goto integer; case SO_LISTENINCQLEN: optval = so->so_incqlen; goto integer; default: if (V_socket_hhh[HHOOK_SOCKET_OPT]->hhh_nhooks > 0) error = hhook_run_socket(so, sopt, HHOOK_SOCKET_OPT); else error = ENOPROTOOPT; break; } } #ifdef MAC bad: #endif CURVNET_RESTORE(); return (error); } int soopt_getm(struct sockopt *sopt, struct mbuf **mp) { struct mbuf *m, *m_prev; int sopt_size = sopt->sopt_valsize; MGET(m, sopt->sopt_td ? M_WAITOK : M_NOWAIT, MT_DATA); if (m == NULL) return ENOBUFS; if (sopt_size > MLEN) { MCLGET(m, sopt->sopt_td ? M_WAITOK : M_NOWAIT); if ((m->m_flags & M_EXT) == 0) { m_free(m); return ENOBUFS; } m->m_len = min(MCLBYTES, sopt_size); } else { m->m_len = min(MLEN, sopt_size); } sopt_size -= m->m_len; *mp = m; m_prev = m; while (sopt_size) { MGET(m, sopt->sopt_td ? M_WAITOK : M_NOWAIT, MT_DATA); if (m == NULL) { m_freem(*mp); return ENOBUFS; } if (sopt_size > MLEN) { MCLGET(m, sopt->sopt_td != NULL ? M_WAITOK : M_NOWAIT); if ((m->m_flags & M_EXT) == 0) { m_freem(m); m_freem(*mp); return ENOBUFS; } m->m_len = min(MCLBYTES, sopt_size); } else { m->m_len = min(MLEN, sopt_size); } sopt_size -= m->m_len; m_prev->m_next = m; m_prev = m; } return (0); } int soopt_mcopyin(struct sockopt *sopt, struct mbuf *m) { struct mbuf *m0 = m; if (sopt->sopt_val == NULL) return (0); while (m != NULL && sopt->sopt_valsize >= m->m_len) { if (sopt->sopt_td != NULL) { int error; error = copyin(sopt->sopt_val, mtod(m, char *), m->m_len); if (error != 0) { m_freem(m0); return(error); } } else bcopy(sopt->sopt_val, mtod(m, char *), m->m_len); sopt->sopt_valsize -= m->m_len; sopt->sopt_val = (char *)sopt->sopt_val + m->m_len; m = m->m_next; } if (m != NULL) /* should be allocated enoughly at ip6_sooptmcopyin() */ panic("ip6_sooptmcopyin"); return (0); } int soopt_mcopyout(struct sockopt *sopt, struct mbuf *m) { struct mbuf *m0 = m; size_t valsize = 0; if (sopt->sopt_val == NULL) return (0); while (m != NULL && sopt->sopt_valsize >= m->m_len) { if (sopt->sopt_td != NULL) { int error; error = copyout(mtod(m, char *), sopt->sopt_val, m->m_len); if (error != 0) { m_freem(m0); return(error); } } else bcopy(mtod(m, char *), sopt->sopt_val, m->m_len); sopt->sopt_valsize -= m->m_len; sopt->sopt_val = (char *)sopt->sopt_val + m->m_len; valsize += m->m_len; m = m->m_next; } if (m != NULL) { /* enough soopt buffer should be given from user-land */ m_freem(m0); return(EINVAL); } sopt->sopt_valsize = valsize; return (0); } /* * sohasoutofband(): protocol notifies socket layer of the arrival of new * out-of-band data, which will then notify socket consumers. */ void sohasoutofband(struct socket *so) { if (so->so_sigio != NULL) pgsigio(&so->so_sigio, SIGURG, 0); selwakeuppri(&so->so_rcv.sb_sel, PSOCK); } int sopoll(struct socket *so, int events, struct ucred *active_cred, struct thread *td) { /* * We do not need to set or assert curvnet as long as everyone uses * sopoll_generic(). */ return (so->so_proto->pr_usrreqs->pru_sopoll(so, events, active_cred, td)); } int sopoll_generic(struct socket *so, int events, struct ucred *active_cred, struct thread *td) { int revents = 0; SOCKBUF_LOCK(&so->so_snd); SOCKBUF_LOCK(&so->so_rcv); if (events & (POLLIN | POLLRDNORM)) if (soreadabledata(so)) revents |= events & (POLLIN | POLLRDNORM); if (events & (POLLOUT | POLLWRNORM)) if (sowriteable(so)) revents |= events & (POLLOUT | POLLWRNORM); if (events & (POLLPRI | POLLRDBAND)) if (so->so_oobmark || (so->so_rcv.sb_state & SBS_RCVATMARK)) revents |= events & (POLLPRI | POLLRDBAND); if ((events & POLLINIGNEOF) == 0) { if (so->so_rcv.sb_state & SBS_CANTRCVMORE) { revents |= events & (POLLIN | POLLRDNORM); if (so->so_snd.sb_state & SBS_CANTSENDMORE) revents |= POLLHUP; } } if (revents == 0) { if (events & (POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND)) { selrecord(td, &so->so_rcv.sb_sel); so->so_rcv.sb_flags |= SB_SEL; } if (events & (POLLOUT | POLLWRNORM)) { selrecord(td, &so->so_snd.sb_sel); so->so_snd.sb_flags |= SB_SEL; } } SOCKBUF_UNLOCK(&so->so_rcv); SOCKBUF_UNLOCK(&so->so_snd); return (revents); } int soo_kqfilter(struct file *fp, struct knote *kn) { struct socket *so = kn->kn_fp->f_data; struct sockbuf *sb; switch (kn->kn_filter) { case EVFILT_READ: if (so->so_options & SO_ACCEPTCONN) kn->kn_fop = &solisten_filtops; else kn->kn_fop = &soread_filtops; sb = &so->so_rcv; break; case EVFILT_WRITE: kn->kn_fop = &sowrite_filtops; sb = &so->so_snd; break; default: return (EINVAL); } SOCKBUF_LOCK(sb); knlist_add(&sb->sb_sel.si_note, kn, 1); sb->sb_flags |= SB_KNOTE; SOCKBUF_UNLOCK(sb); return (0); } /* * Some routines that return EOPNOTSUPP for entry points that are not * supported by a protocol. Fill in as needed. */ int pru_accept_notsupp(struct socket *so, struct sockaddr **nam) { return EOPNOTSUPP; } int pru_attach_notsupp(struct socket *so, int proto, struct thread *td) { return EOPNOTSUPP; } int pru_bind_notsupp(struct socket *so, struct sockaddr *nam, struct thread *td) { return EOPNOTSUPP; } int pru_bindat_notsupp(int fd, struct socket *so, struct sockaddr *nam, struct thread *td) { return EOPNOTSUPP; } int pru_connect_notsupp(struct socket *so, struct sockaddr *nam, struct thread *td) { return EOPNOTSUPP; } int pru_connectat_notsupp(int fd, struct socket *so, struct sockaddr *nam, struct thread *td) { return EOPNOTSUPP; } int pru_connect2_notsupp(struct socket *so1, struct socket *so2) { return EOPNOTSUPP; } int pru_control_notsupp(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp, struct thread *td) { return EOPNOTSUPP; } int pru_disconnect_notsupp(struct socket *so) { return EOPNOTSUPP; } int pru_listen_notsupp(struct socket *so, int backlog, struct thread *td) { return EOPNOTSUPP; } int pru_peeraddr_notsupp(struct socket *so, struct sockaddr **nam) { return EOPNOTSUPP; } int pru_rcvd_notsupp(struct socket *so, int flags) { return EOPNOTSUPP; } int pru_rcvoob_notsupp(struct socket *so, struct mbuf *m, int flags) { return EOPNOTSUPP; } int pru_send_notsupp(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *td) { return EOPNOTSUPP; } int pru_ready_notsupp(struct socket *so, struct mbuf *m, int count) { return (EOPNOTSUPP); } /* * This isn't really a ``null'' operation, but it's the default one and * doesn't do anything destructive. */ int pru_sense_null(struct socket *so, struct stat *sb) { sb->st_blksize = so->so_snd.sb_hiwat; return 0; } int pru_shutdown_notsupp(struct socket *so) { return EOPNOTSUPP; } int pru_sockaddr_notsupp(struct socket *so, struct sockaddr **nam) { return EOPNOTSUPP; } int pru_sosend_notsupp(struct socket *so, struct sockaddr *addr, struct uio *uio, struct mbuf *top, struct mbuf *control, int flags, struct thread *td) { return EOPNOTSUPP; } int pru_soreceive_notsupp(struct socket *so, struct sockaddr **paddr, struct uio *uio, struct mbuf **mp0, struct mbuf **controlp, int *flagsp) { return EOPNOTSUPP; } int pru_sopoll_notsupp(struct socket *so, int events, struct ucred *cred, struct thread *td) { return EOPNOTSUPP; } static void filt_sordetach(struct knote *kn) { struct socket *so = kn->kn_fp->f_data; SOCKBUF_LOCK(&so->so_rcv); knlist_remove(&so->so_rcv.sb_sel.si_note, kn, 1); if (knlist_empty(&so->so_rcv.sb_sel.si_note)) so->so_rcv.sb_flags &= ~SB_KNOTE; SOCKBUF_UNLOCK(&so->so_rcv); } /*ARGSUSED*/ static int filt_soread(struct knote *kn, long hint) { struct socket *so; so = kn->kn_fp->f_data; SOCKBUF_LOCK_ASSERT(&so->so_rcv); kn->kn_data = sbavail(&so->so_rcv) - so->so_rcv.sb_ctl; if (so->so_rcv.sb_state & SBS_CANTRCVMORE) { kn->kn_flags |= EV_EOF; kn->kn_fflags = so->so_error; return (1); } else if (so->so_error) /* temporary udp error */ return (1); if (kn->kn_sfflags & NOTE_LOWAT) { if (kn->kn_data >= kn->kn_sdata) return 1; } else { if (sbavail(&so->so_rcv) >= so->so_rcv.sb_lowat) return 1; } /* This hook returning non-zero indicates an event, not error */ return (hhook_run_socket(so, NULL, HHOOK_FILT_SOREAD)); } static void filt_sowdetach(struct knote *kn) { struct socket *so = kn->kn_fp->f_data; SOCKBUF_LOCK(&so->so_snd); knlist_remove(&so->so_snd.sb_sel.si_note, kn, 1); if (knlist_empty(&so->so_snd.sb_sel.si_note)) so->so_snd.sb_flags &= ~SB_KNOTE; SOCKBUF_UNLOCK(&so->so_snd); } /*ARGSUSED*/ static int filt_sowrite(struct knote *kn, long hint) { struct socket *so; so = kn->kn_fp->f_data; SOCKBUF_LOCK_ASSERT(&so->so_snd); kn->kn_data = sbspace(&so->so_snd); hhook_run_socket(so, kn, HHOOK_FILT_SOWRITE); if (so->so_snd.sb_state & SBS_CANTSENDMORE) { kn->kn_flags |= EV_EOF; kn->kn_fflags = so->so_error; return (1); } else if (so->so_error) /* temporary udp error */ return (1); else if (((so->so_state & SS_ISCONNECTED) == 0) && (so->so_proto->pr_flags & PR_CONNREQUIRED)) return (0); else if (kn->kn_sfflags & NOTE_LOWAT) return (kn->kn_data >= kn->kn_sdata); else return (kn->kn_data >= so->so_snd.sb_lowat); } /*ARGSUSED*/ static int filt_solisten(struct knote *kn, long hint) { struct socket *so = kn->kn_fp->f_data; kn->kn_data = so->so_qlen; return (!TAILQ_EMPTY(&so->so_comp)); } int socheckuid(struct socket *so, uid_t uid) { if (so == NULL) return (EPERM); if (so->so_cred->cr_uid != uid) return (EPERM); return (0); } /* * These functions are used by protocols to notify the socket layer (and its * consumers) of state changes in the sockets driven by protocol-side events. */ /* * Procedures to manipulate state flags of socket and do appropriate wakeups. * * Normal sequence from the active (originating) side is that * soisconnecting() is called during processing of connect() call, resulting * in an eventual call to soisconnected() if/when the connection is * established. When the connection is torn down soisdisconnecting() is * called during processing of disconnect() call, and soisdisconnected() is * called when the connection to the peer is totally severed. The semantics * of these routines are such that connectionless protocols can call * soisconnected() and soisdisconnected() only, bypassing the in-progress * calls when setting up a ``connection'' takes no time. * * From the passive side, a socket is created with two queues of sockets: * so_incomp for connections in progress and so_comp for connections already * made and awaiting user acceptance. As a protocol is preparing incoming * connections, it creates a socket structure queued on so_incomp by calling * sonewconn(). When the connection is established, soisconnected() is * called, and transfers the socket structure to so_comp, making it available * to accept(). * * If a socket is closed with sockets on either so_incomp or so_comp, these * sockets are dropped. * * If higher-level protocols are implemented in the kernel, the wakeups done * here will sometimes cause software-interrupt process scheduling. */ void soisconnecting(struct socket *so) { SOCK_LOCK(so); so->so_state &= ~(SS_ISCONNECTED|SS_ISDISCONNECTING); so->so_state |= SS_ISCONNECTING; SOCK_UNLOCK(so); } void soisconnected(struct socket *so) { struct socket *head; int ret; restart: ACCEPT_LOCK(); SOCK_LOCK(so); so->so_state &= ~(SS_ISCONNECTING|SS_ISDISCONNECTING|SS_ISCONFIRMING); so->so_state |= SS_ISCONNECTED; head = so->so_head; if (head != NULL && (so->so_qstate & SQ_INCOMP)) { if ((so->so_options & SO_ACCEPTFILTER) == 0) { SOCK_UNLOCK(so); TAILQ_REMOVE(&head->so_incomp, so, so_list); head->so_incqlen--; so->so_qstate &= ~SQ_INCOMP; TAILQ_INSERT_TAIL(&head->so_comp, so, so_list); head->so_qlen++; so->so_qstate |= SQ_COMP; ACCEPT_UNLOCK(); sorwakeup(head); wakeup_one(&head->so_timeo); } else { ACCEPT_UNLOCK(); soupcall_set(so, SO_RCV, head->so_accf->so_accept_filter->accf_callback, head->so_accf->so_accept_filter_arg); so->so_options &= ~SO_ACCEPTFILTER; ret = head->so_accf->so_accept_filter->accf_callback(so, head->so_accf->so_accept_filter_arg, M_NOWAIT); if (ret == SU_ISCONNECTED) soupcall_clear(so, SO_RCV); SOCK_UNLOCK(so); if (ret == SU_ISCONNECTED) goto restart; } return; } SOCK_UNLOCK(so); ACCEPT_UNLOCK(); wakeup(&so->so_timeo); sorwakeup(so); sowwakeup(so); } void soisdisconnecting(struct socket *so) { /* * Note: This code assumes that SOCK_LOCK(so) and * SOCKBUF_LOCK(&so->so_rcv) are the same. */ SOCKBUF_LOCK(&so->so_rcv); so->so_state &= ~SS_ISCONNECTING; so->so_state |= SS_ISDISCONNECTING; so->so_rcv.sb_state |= SBS_CANTRCVMORE; sorwakeup_locked(so); SOCKBUF_LOCK(&so->so_snd); so->so_snd.sb_state |= SBS_CANTSENDMORE; sowwakeup_locked(so); wakeup(&so->so_timeo); } void soisdisconnected(struct socket *so) { /* * Note: This code assumes that SOCK_LOCK(so) and * SOCKBUF_LOCK(&so->so_rcv) are the same. */ SOCKBUF_LOCK(&so->so_rcv); so->so_state &= ~(SS_ISCONNECTING|SS_ISCONNECTED|SS_ISDISCONNECTING); so->so_state |= SS_ISDISCONNECTED; so->so_rcv.sb_state |= SBS_CANTRCVMORE; sorwakeup_locked(so); SOCKBUF_LOCK(&so->so_snd); so->so_snd.sb_state |= SBS_CANTSENDMORE; sbdrop_locked(&so->so_snd, sbused(&so->so_snd)); sowwakeup_locked(so); wakeup(&so->so_timeo); } /* * Make a copy of a sockaddr in a malloced buffer of type M_SONAME. */ struct sockaddr * sodupsockaddr(const struct sockaddr *sa, int mflags) { struct sockaddr *sa2; sa2 = malloc(sa->sa_len, M_SONAME, mflags); if (sa2) bcopy(sa, sa2, sa->sa_len); return sa2; } /* * Register per-socket buffer upcalls. */ void soupcall_set(struct socket *so, int which, int (*func)(struct socket *, void *, int), void *arg) { struct sockbuf *sb; switch (which) { case SO_RCV: sb = &so->so_rcv; break; case SO_SND: sb = &so->so_snd; break; default: panic("soupcall_set: bad which"); } SOCKBUF_LOCK_ASSERT(sb); #if 0 /* XXX: accf_http actually wants to do this on purpose. */ KASSERT(sb->sb_upcall == NULL, ("soupcall_set: overwriting upcall")); #endif sb->sb_upcall = func; sb->sb_upcallarg = arg; sb->sb_flags |= SB_UPCALL; } void soupcall_clear(struct socket *so, int which) { struct sockbuf *sb; switch (which) { case SO_RCV: sb = &so->so_rcv; break; case SO_SND: sb = &so->so_snd; break; default: panic("soupcall_clear: bad which"); } SOCKBUF_LOCK_ASSERT(sb); KASSERT(sb->sb_upcall != NULL, ("soupcall_clear: no upcall to clear")); sb->sb_upcall = NULL; sb->sb_upcallarg = NULL; sb->sb_flags &= ~SB_UPCALL; } /* * Create an external-format (``xsocket'') structure using the information in * the kernel-format socket structure pointed to by so. This is done to * reduce the spew of irrelevant information over this interface, to isolate * user code from changes in the kernel structure, and potentially to provide * information-hiding if we decide that some of this information should be * hidden from users. */ void sotoxsocket(struct socket *so, struct xsocket *xso) { xso->xso_len = sizeof *xso; xso->xso_so = so; xso->so_type = so->so_type; xso->so_options = so->so_options; xso->so_linger = so->so_linger; xso->so_state = so->so_state; xso->so_pcb = so->so_pcb; xso->xso_protocol = so->so_proto->pr_protocol; xso->xso_family = so->so_proto->pr_domain->dom_family; xso->so_qlen = so->so_qlen; xso->so_incqlen = so->so_incqlen; xso->so_qlimit = so->so_qlimit; xso->so_timeo = so->so_timeo; xso->so_error = so->so_error; xso->so_pgid = so->so_sigio ? so->so_sigio->sio_pgid : 0; xso->so_oobmark = so->so_oobmark; sbtoxsockbuf(&so->so_snd, &xso->so_snd); sbtoxsockbuf(&so->so_rcv, &xso->so_rcv); xso->so_uid = so->so_cred->cr_uid; } /* * Socket accessor functions to provide external consumers with * a safe interface to socket state * */ void so_listeners_apply_all(struct socket *so, void (*func)(struct socket *, void *), void *arg) { TAILQ_FOREACH(so, &so->so_comp, so_list) func(so, arg); } struct sockbuf * so_sockbuf_rcv(struct socket *so) { return (&so->so_rcv); } struct sockbuf * so_sockbuf_snd(struct socket *so) { return (&so->so_snd); } int so_state_get(const struct socket *so) { return (so->so_state); } void so_state_set(struct socket *so, int val) { so->so_state = val; } int so_options_get(const struct socket *so) { return (so->so_options); } void so_options_set(struct socket *so, int val) { so->so_options = val; } int so_error_get(const struct socket *so) { return (so->so_error); } void so_error_set(struct socket *so, int val) { so->so_error = val; } int so_linger_get(const struct socket *so) { return (so->so_linger); } void so_linger_set(struct socket *so, int val) { so->so_linger = val; } struct protosw * so_protosw_get(const struct socket *so) { return (so->so_proto); } void so_protosw_set(struct socket *so, struct protosw *val) { so->so_proto = val; } void so_sorwakeup(struct socket *so) { sorwakeup(so); } void so_sowwakeup(struct socket *so) { sowwakeup(so); } void so_sorwakeup_locked(struct socket *so) { sorwakeup_locked(so); } void so_sowwakeup_locked(struct socket *so) { sowwakeup_locked(so); } void so_lock(struct socket *so) { SOCK_LOCK(so); } void so_unlock(struct socket *so) { SOCK_UNLOCK(so); } Index: projects/sendfile/sys/netpfil/pf/pf_ioctl.c =================================================================== --- projects/sendfile/sys/netpfil/pf/pf_ioctl.c (revision 274716) +++ projects/sendfile/sys/netpfil/pf/pf_ioctl.c (revision 274717) @@ -1,3815 +1,3815 @@ /*- * Copyright (c) 2001 Daniel Hartmeier * Copyright (c) 2002,2003 Henning Brauer * Copyright (c) 2012 Gleb Smirnoff * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - 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. * * 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 HOLDERS 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. * * Effort sponsored in part by the Defense Advanced Research Projects * Agency (DARPA) and Air Force Research Laboratory, Air Force * Materiel Command, USAF, under agreement number F30602-01-2-0537. * * $OpenBSD: pf_ioctl.c,v 1.213 2009/02/15 21:46:12 mbalmer Exp $ */ #include __FBSDID("$FreeBSD$"); #include "opt_inet.h" #include "opt_inet6.h" #include "opt_bpf.h" #include "opt_pf.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #ifdef INET6 #include #endif /* INET6 */ #ifdef ALTQ #include #endif static int pfattach(void); static struct pf_pool *pf_get_pool(char *, u_int32_t, u_int8_t, u_int32_t, u_int8_t, u_int8_t, u_int8_t); static void pf_mv_pool(struct pf_palist *, struct pf_palist *); static void pf_empty_pool(struct pf_palist *); static int pfioctl(struct cdev *, u_long, caddr_t, int, struct thread *); #ifdef ALTQ static int pf_begin_altq(u_int32_t *); static int pf_rollback_altq(u_int32_t); static int pf_commit_altq(u_int32_t); static int pf_enable_altq(struct pf_altq *); static int pf_disable_altq(struct pf_altq *); static u_int32_t pf_qname2qid(char *); static void pf_qid_unref(u_int32_t); #endif /* ALTQ */ static int pf_begin_rules(u_int32_t *, int, const char *); static int pf_rollback_rules(u_int32_t, int, char *); static int pf_setup_pfsync_matching(struct pf_ruleset *); static void pf_hash_rule(MD5_CTX *, struct pf_rule *); static void pf_hash_rule_addr(MD5_CTX *, struct pf_rule_addr *); static int pf_commit_rules(u_int32_t, int, char *); static int pf_addr_setup(struct pf_ruleset *, struct pf_addr_wrap *, sa_family_t); static void pf_addr_copyout(struct pf_addr_wrap *); VNET_DEFINE(struct pf_rule, pf_default_rule); #ifdef ALTQ static VNET_DEFINE(int, pf_altq_running); #define V_pf_altq_running VNET(pf_altq_running) #endif #define TAGID_MAX 50000 struct pf_tagname { TAILQ_ENTRY(pf_tagname) entries; char name[PF_TAG_NAME_SIZE]; uint16_t tag; int ref; }; TAILQ_HEAD(pf_tags, pf_tagname); #define V_pf_tags VNET(pf_tags) VNET_DEFINE(struct pf_tags, pf_tags); #define V_pf_qids VNET(pf_qids) VNET_DEFINE(struct pf_tags, pf_qids); static MALLOC_DEFINE(M_PFTAG, "pf_tag", "pf(4) tag names"); static MALLOC_DEFINE(M_PFALTQ, "pf_altq", "pf(4) altq configuration db"); static MALLOC_DEFINE(M_PFRULE, "pf_rule", "pf(4) rules"); #if (PF_QNAME_SIZE != PF_TAG_NAME_SIZE) #error PF_QNAME_SIZE must be equal to PF_TAG_NAME_SIZE #endif static u_int16_t tagname2tag(struct pf_tags *, char *); static u_int16_t pf_tagname2tag(char *); static void tag_unref(struct pf_tags *, u_int16_t); #define DPFPRINTF(n, x) if (V_pf_status.debug >= (n)) printf x struct cdev *pf_dev; /* * XXX - These are new and need to be checked when moveing to a new version */ static void pf_clear_states(void); static int pf_clear_tables(void); static void pf_clear_srcnodes(struct pf_src_node *); static void pf_kill_srcnodes(struct pfioc_src_node_kill *); static void pf_tbladdr_copyout(struct pf_addr_wrap *); /* * Wrapper functions for pfil(9) hooks */ #ifdef INET static int pf_check_in(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, struct inpcb *inp); static int pf_check_out(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, struct inpcb *inp); #endif #ifdef INET6 static int pf_check6_in(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, struct inpcb *inp); static int pf_check6_out(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, struct inpcb *inp); #endif static int hook_pf(void); static int dehook_pf(void); static int shutdown_pf(void); static int pf_load(void); static int pf_unload(void); static struct cdevsw pf_cdevsw = { .d_ioctl = pfioctl, .d_name = PF_NAME, .d_version = D_VERSION, }; static volatile VNET_DEFINE(int, pf_pfil_hooked); #define V_pf_pfil_hooked VNET(pf_pfil_hooked) VNET_DEFINE(int, pf_end_threads); struct rwlock pf_rules_lock; struct sx pf_ioctl_lock; /* pfsync */ pfsync_state_import_t *pfsync_state_import_ptr = NULL; pfsync_insert_state_t *pfsync_insert_state_ptr = NULL; pfsync_update_state_t *pfsync_update_state_ptr = NULL; pfsync_delete_state_t *pfsync_delete_state_ptr = NULL; pfsync_clear_states_t *pfsync_clear_states_ptr = NULL; pfsync_defer_t *pfsync_defer_ptr = NULL; /* pflog */ pflog_packet_t *pflog_packet_ptr = NULL; static int pfattach(void) { u_int32_t *my_timeout = V_pf_default_rule.timeout; int error; if (IS_DEFAULT_VNET(curvnet)) pf_mtag_initialize(); pf_initialize(); pfr_initialize(); pfi_initialize(); pf_normalize_init(); V_pf_limits[PF_LIMIT_STATES].limit = PFSTATE_HIWAT; V_pf_limits[PF_LIMIT_SRC_NODES].limit = PFSNODE_HIWAT; RB_INIT(&V_pf_anchors); pf_init_ruleset(&pf_main_ruleset); /* default rule should never be garbage collected */ V_pf_default_rule.entries.tqe_prev = &V_pf_default_rule.entries.tqe_next; #ifdef PF_DEFAULT_TO_DROP V_pf_default_rule.action = PF_DROP; #else V_pf_default_rule.action = PF_PASS; #endif V_pf_default_rule.nr = -1; V_pf_default_rule.rtableid = -1; V_pf_default_rule.states_cur = counter_u64_alloc(M_WAITOK); V_pf_default_rule.states_tot = counter_u64_alloc(M_WAITOK); V_pf_default_rule.src_nodes = counter_u64_alloc(M_WAITOK); /* initialize default timeouts */ my_timeout[PFTM_TCP_FIRST_PACKET] = PFTM_TCP_FIRST_PACKET_VAL; my_timeout[PFTM_TCP_OPENING] = PFTM_TCP_OPENING_VAL; my_timeout[PFTM_TCP_ESTABLISHED] = PFTM_TCP_ESTABLISHED_VAL; my_timeout[PFTM_TCP_CLOSING] = PFTM_TCP_CLOSING_VAL; my_timeout[PFTM_TCP_FIN_WAIT] = PFTM_TCP_FIN_WAIT_VAL; my_timeout[PFTM_TCP_CLOSED] = PFTM_TCP_CLOSED_VAL; my_timeout[PFTM_UDP_FIRST_PACKET] = PFTM_UDP_FIRST_PACKET_VAL; my_timeout[PFTM_UDP_SINGLE] = PFTM_UDP_SINGLE_VAL; my_timeout[PFTM_UDP_MULTIPLE] = PFTM_UDP_MULTIPLE_VAL; my_timeout[PFTM_ICMP_FIRST_PACKET] = PFTM_ICMP_FIRST_PACKET_VAL; my_timeout[PFTM_ICMP_ERROR_REPLY] = PFTM_ICMP_ERROR_REPLY_VAL; my_timeout[PFTM_OTHER_FIRST_PACKET] = PFTM_OTHER_FIRST_PACKET_VAL; my_timeout[PFTM_OTHER_SINGLE] = PFTM_OTHER_SINGLE_VAL; my_timeout[PFTM_OTHER_MULTIPLE] = PFTM_OTHER_MULTIPLE_VAL; my_timeout[PFTM_FRAG] = PFTM_FRAG_VAL; my_timeout[PFTM_INTERVAL] = PFTM_INTERVAL_VAL; my_timeout[PFTM_SRC_NODE] = PFTM_SRC_NODE_VAL; my_timeout[PFTM_TS_DIFF] = PFTM_TS_DIFF_VAL; my_timeout[PFTM_ADAPTIVE_START] = PFSTATE_ADAPT_START; my_timeout[PFTM_ADAPTIVE_END] = PFSTATE_ADAPT_END; bzero(&V_pf_status, sizeof(V_pf_status)); V_pf_status.debug = PF_DEBUG_URGENT; V_pf_pfil_hooked = 0; /* XXX do our best to avoid a conflict */ V_pf_status.hostid = arc4random(); for (int i = 0; i < PFRES_MAX; i++) V_pf_status.counters[i] = counter_u64_alloc(M_WAITOK); for (int i = 0; i < LCNT_MAX; i++) V_pf_status.lcounters[i] = counter_u64_alloc(M_WAITOK); for (int i = 0; i < FCNT_MAX; i++) V_pf_status.fcounters[i] = counter_u64_alloc(M_WAITOK); for (int i = 0; i < SCNT_MAX; i++) V_pf_status.scounters[i] = counter_u64_alloc(M_WAITOK); if ((error = kproc_create(pf_purge_thread, curvnet, NULL, 0, 0, "pf purge")) != 0) /* XXXGL: leaked all above. */ return (error); if ((error = swi_add(NULL, "pf send", pf_intr, curvnet, SWI_NET, INTR_MPSAFE, &V_pf_swi_cookie)) != 0) /* XXXGL: leaked all above. */ return (error); return (0); } static struct pf_pool * pf_get_pool(char *anchor, u_int32_t ticket, u_int8_t rule_action, u_int32_t rule_number, u_int8_t r_last, u_int8_t active, u_int8_t check_ticket) { struct pf_ruleset *ruleset; struct pf_rule *rule; int rs_num; ruleset = pf_find_ruleset(anchor); if (ruleset == NULL) return (NULL); rs_num = pf_get_ruleset_number(rule_action); if (rs_num >= PF_RULESET_MAX) return (NULL); if (active) { if (check_ticket && ticket != ruleset->rules[rs_num].active.ticket) return (NULL); if (r_last) rule = TAILQ_LAST(ruleset->rules[rs_num].active.ptr, pf_rulequeue); else rule = TAILQ_FIRST(ruleset->rules[rs_num].active.ptr); } else { if (check_ticket && ticket != ruleset->rules[rs_num].inactive.ticket) return (NULL); if (r_last) rule = TAILQ_LAST(ruleset->rules[rs_num].inactive.ptr, pf_rulequeue); else rule = TAILQ_FIRST(ruleset->rules[rs_num].inactive.ptr); } if (!r_last) { while ((rule != NULL) && (rule->nr != rule_number)) rule = TAILQ_NEXT(rule, entries); } if (rule == NULL) return (NULL); return (&rule->rpool); } static void pf_mv_pool(struct pf_palist *poola, struct pf_palist *poolb) { struct pf_pooladdr *mv_pool_pa; while ((mv_pool_pa = TAILQ_FIRST(poola)) != NULL) { TAILQ_REMOVE(poola, mv_pool_pa, entries); TAILQ_INSERT_TAIL(poolb, mv_pool_pa, entries); } } static void pf_empty_pool(struct pf_palist *poola) { struct pf_pooladdr *pa; while ((pa = TAILQ_FIRST(poola)) != NULL) { switch (pa->addr.type) { case PF_ADDR_DYNIFTL: pfi_dynaddr_remove(pa->addr.p.dyn); break; case PF_ADDR_TABLE: /* XXX: this could be unfinished pooladdr on pabuf */ if (pa->addr.p.tbl != NULL) pfr_detach_table(pa->addr.p.tbl); break; } if (pa->kif) pfi_kif_unref(pa->kif); TAILQ_REMOVE(poola, pa, entries); free(pa, M_PFRULE); } } static void pf_unlink_rule(struct pf_rulequeue *rulequeue, struct pf_rule *rule) { PF_RULES_WASSERT(); TAILQ_REMOVE(rulequeue, rule, entries); PF_UNLNKDRULES_LOCK(); rule->rule_flag |= PFRULE_REFS; TAILQ_INSERT_TAIL(&V_pf_unlinked_rules, rule, entries); PF_UNLNKDRULES_UNLOCK(); } void pf_free_rule(struct pf_rule *rule) { PF_RULES_WASSERT(); if (rule->tag) tag_unref(&V_pf_tags, rule->tag); if (rule->match_tag) tag_unref(&V_pf_tags, rule->match_tag); #ifdef ALTQ if (rule->pqid != rule->qid) pf_qid_unref(rule->pqid); pf_qid_unref(rule->qid); #endif switch (rule->src.addr.type) { case PF_ADDR_DYNIFTL: pfi_dynaddr_remove(rule->src.addr.p.dyn); break; case PF_ADDR_TABLE: pfr_detach_table(rule->src.addr.p.tbl); break; } switch (rule->dst.addr.type) { case PF_ADDR_DYNIFTL: pfi_dynaddr_remove(rule->dst.addr.p.dyn); break; case PF_ADDR_TABLE: pfr_detach_table(rule->dst.addr.p.tbl); break; } if (rule->overload_tbl) pfr_detach_table(rule->overload_tbl); if (rule->kif) pfi_kif_unref(rule->kif); pf_anchor_remove(rule); pf_empty_pool(&rule->rpool.list); counter_u64_free(rule->states_cur); counter_u64_free(rule->states_tot); counter_u64_free(rule->src_nodes); free(rule, M_PFRULE); } static u_int16_t tagname2tag(struct pf_tags *head, char *tagname) { struct pf_tagname *tag, *p = NULL; u_int16_t new_tagid = 1; PF_RULES_WASSERT(); TAILQ_FOREACH(tag, head, entries) if (strcmp(tagname, tag->name) == 0) { tag->ref++; return (tag->tag); } /* * to avoid fragmentation, we do a linear search from the beginning * and take the first free slot we find. if there is none or the list * is empty, append a new entry at the end. */ /* new entry */ if (!TAILQ_EMPTY(head)) for (p = TAILQ_FIRST(head); p != NULL && p->tag == new_tagid; p = TAILQ_NEXT(p, entries)) new_tagid = p->tag + 1; if (new_tagid > TAGID_MAX) return (0); /* allocate and fill new struct pf_tagname */ tag = malloc(sizeof(*tag), M_PFTAG, M_NOWAIT|M_ZERO); if (tag == NULL) return (0); strlcpy(tag->name, tagname, sizeof(tag->name)); tag->tag = new_tagid; tag->ref++; if (p != NULL) /* insert new entry before p */ TAILQ_INSERT_BEFORE(p, tag, entries); else /* either list empty or no free slot in between */ TAILQ_INSERT_TAIL(head, tag, entries); return (tag->tag); } static void tag_unref(struct pf_tags *head, u_int16_t tag) { struct pf_tagname *p, *next; PF_RULES_WASSERT(); for (p = TAILQ_FIRST(head); p != NULL; p = next) { next = TAILQ_NEXT(p, entries); if (tag == p->tag) { if (--p->ref == 0) { TAILQ_REMOVE(head, p, entries); free(p, M_PFTAG); } break; } } } static u_int16_t pf_tagname2tag(char *tagname) { return (tagname2tag(&V_pf_tags, tagname)); } #ifdef ALTQ static u_int32_t pf_qname2qid(char *qname) { return ((u_int32_t)tagname2tag(&V_pf_qids, qname)); } static void pf_qid_unref(u_int32_t qid) { tag_unref(&V_pf_qids, (u_int16_t)qid); } static int pf_begin_altq(u_int32_t *ticket) { struct pf_altq *altq; int error = 0; PF_RULES_WASSERT(); /* Purge the old altq list */ while ((altq = TAILQ_FIRST(V_pf_altqs_inactive)) != NULL) { TAILQ_REMOVE(V_pf_altqs_inactive, altq, entries); if (altq->qname[0] == 0 && (altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { /* detach and destroy the discipline */ error = altq_remove(altq); } else pf_qid_unref(altq->qid); free(altq, M_PFALTQ); } if (error) return (error); *ticket = ++V_ticket_altqs_inactive; V_altqs_inactive_open = 1; return (0); } static int pf_rollback_altq(u_int32_t ticket) { struct pf_altq *altq; int error = 0; PF_RULES_WASSERT(); if (!V_altqs_inactive_open || ticket != V_ticket_altqs_inactive) return (0); /* Purge the old altq list */ while ((altq = TAILQ_FIRST(V_pf_altqs_inactive)) != NULL) { TAILQ_REMOVE(V_pf_altqs_inactive, altq, entries); if (altq->qname[0] == 0 && (altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { /* detach and destroy the discipline */ error = altq_remove(altq); } else pf_qid_unref(altq->qid); free(altq, M_PFALTQ); } V_altqs_inactive_open = 0; return (error); } static int pf_commit_altq(u_int32_t ticket) { struct pf_altqqueue *old_altqs; struct pf_altq *altq; int err, error = 0; PF_RULES_WASSERT(); if (!V_altqs_inactive_open || ticket != V_ticket_altqs_inactive) return (EBUSY); /* swap altqs, keep the old. */ old_altqs = V_pf_altqs_active; V_pf_altqs_active = V_pf_altqs_inactive; V_pf_altqs_inactive = old_altqs; V_ticket_altqs_active = V_ticket_altqs_inactive; /* Attach new disciplines */ TAILQ_FOREACH(altq, V_pf_altqs_active, entries) { if (altq->qname[0] == 0 && (altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { /* attach the discipline */ error = altq_pfattach(altq); if (error == 0 && V_pf_altq_running) error = pf_enable_altq(altq); if (error != 0) return (error); } } /* Purge the old altq list */ while ((altq = TAILQ_FIRST(V_pf_altqs_inactive)) != NULL) { TAILQ_REMOVE(V_pf_altqs_inactive, altq, entries); if (altq->qname[0] == 0 && (altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { /* detach and destroy the discipline */ if (V_pf_altq_running) error = pf_disable_altq(altq); err = altq_pfdetach(altq); if (err != 0 && error == 0) error = err; err = altq_remove(altq); if (err != 0 && error == 0) error = err; } else pf_qid_unref(altq->qid); free(altq, M_PFALTQ); } V_altqs_inactive_open = 0; return (error); } static int pf_enable_altq(struct pf_altq *altq) { struct ifnet *ifp; struct tb_profile tb; int error = 0; if ((ifp = ifunit(altq->ifname)) == NULL) return (EINVAL); if (ifp->if_snd.altq_type != ALTQT_NONE) error = altq_enable(&ifp->if_snd); /* set tokenbucket regulator */ if (error == 0 && ifp != NULL && ALTQ_IS_ENABLED(&ifp->if_snd)) { tb.rate = altq->ifbandwidth; tb.depth = altq->tbrsize; error = tbr_set(&ifp->if_snd, &tb); } return (error); } static int pf_disable_altq(struct pf_altq *altq) { struct ifnet *ifp; struct tb_profile tb; int error; if ((ifp = ifunit(altq->ifname)) == NULL) return (EINVAL); /* * when the discipline is no longer referenced, it was overridden * by a new one. if so, just return. */ if (altq->altq_disc != ifp->if_snd.altq_disc) return (0); error = altq_disable(&ifp->if_snd); if (error == 0) { /* clear tokenbucket regulator */ tb.rate = 0; error = tbr_set(&ifp->if_snd, &tb); } return (error); } void pf_altq_ifnet_event(struct ifnet *ifp, int remove) { struct ifnet *ifp1; struct pf_altq *a1, *a2, *a3; u_int32_t ticket; int error = 0; /* Interrupt userland queue modifications */ if (V_altqs_inactive_open) pf_rollback_altq(V_ticket_altqs_inactive); /* Start new altq ruleset */ if (pf_begin_altq(&ticket)) return; /* Copy the current active set */ TAILQ_FOREACH(a1, V_pf_altqs_active, entries) { a2 = malloc(sizeof(*a2), M_PFALTQ, M_NOWAIT); if (a2 == NULL) { error = ENOMEM; break; } bcopy(a1, a2, sizeof(struct pf_altq)); if (a2->qname[0] != 0) { if ((a2->qid = pf_qname2qid(a2->qname)) == 0) { error = EBUSY; free(a2, M_PFALTQ); break; } a2->altq_disc = NULL; TAILQ_FOREACH(a3, V_pf_altqs_inactive, entries) { if (strncmp(a3->ifname, a2->ifname, IFNAMSIZ) == 0 && a3->qname[0] == 0) { a2->altq_disc = a3->altq_disc; break; } } } /* Deactivate the interface in question */ a2->local_flags &= ~PFALTQ_FLAG_IF_REMOVED; if ((ifp1 = ifunit(a2->ifname)) == NULL || (remove && ifp1 == ifp)) { a2->local_flags |= PFALTQ_FLAG_IF_REMOVED; } else { error = altq_add(a2); if (ticket != V_ticket_altqs_inactive) error = EBUSY; if (error) { free(a2, M_PFALTQ); break; } } TAILQ_INSERT_TAIL(V_pf_altqs_inactive, a2, entries); } if (error != 0) pf_rollback_altq(ticket); else pf_commit_altq(ticket); } #endif /* ALTQ */ static int pf_begin_rules(u_int32_t *ticket, int rs_num, const char *anchor) { struct pf_ruleset *rs; struct pf_rule *rule; PF_RULES_WASSERT(); if (rs_num < 0 || rs_num >= PF_RULESET_MAX) return (EINVAL); rs = pf_find_or_create_ruleset(anchor); if (rs == NULL) return (EINVAL); while ((rule = TAILQ_FIRST(rs->rules[rs_num].inactive.ptr)) != NULL) { pf_unlink_rule(rs->rules[rs_num].inactive.ptr, rule); rs->rules[rs_num].inactive.rcount--; } *ticket = ++rs->rules[rs_num].inactive.ticket; rs->rules[rs_num].inactive.open = 1; return (0); } static int pf_rollback_rules(u_int32_t ticket, int rs_num, char *anchor) { struct pf_ruleset *rs; struct pf_rule *rule; PF_RULES_WASSERT(); if (rs_num < 0 || rs_num >= PF_RULESET_MAX) return (EINVAL); rs = pf_find_ruleset(anchor); if (rs == NULL || !rs->rules[rs_num].inactive.open || rs->rules[rs_num].inactive.ticket != ticket) return (0); while ((rule = TAILQ_FIRST(rs->rules[rs_num].inactive.ptr)) != NULL) { pf_unlink_rule(rs->rules[rs_num].inactive.ptr, rule); rs->rules[rs_num].inactive.rcount--; } rs->rules[rs_num].inactive.open = 0; return (0); } #define PF_MD5_UPD(st, elm) \ MD5Update(ctx, (u_int8_t *) &(st)->elm, sizeof((st)->elm)) #define PF_MD5_UPD_STR(st, elm) \ MD5Update(ctx, (u_int8_t *) (st)->elm, strlen((st)->elm)) #define PF_MD5_UPD_HTONL(st, elm, stor) do { \ (stor) = htonl((st)->elm); \ MD5Update(ctx, (u_int8_t *) &(stor), sizeof(u_int32_t));\ } while (0) #define PF_MD5_UPD_HTONS(st, elm, stor) do { \ (stor) = htons((st)->elm); \ MD5Update(ctx, (u_int8_t *) &(stor), sizeof(u_int16_t));\ } while (0) static void pf_hash_rule_addr(MD5_CTX *ctx, struct pf_rule_addr *pfr) { PF_MD5_UPD(pfr, addr.type); switch (pfr->addr.type) { case PF_ADDR_DYNIFTL: PF_MD5_UPD(pfr, addr.v.ifname); PF_MD5_UPD(pfr, addr.iflags); break; case PF_ADDR_TABLE: PF_MD5_UPD(pfr, addr.v.tblname); break; case PF_ADDR_ADDRMASK: /* XXX ignore af? */ PF_MD5_UPD(pfr, addr.v.a.addr.addr32); PF_MD5_UPD(pfr, addr.v.a.mask.addr32); break; } PF_MD5_UPD(pfr, port[0]); PF_MD5_UPD(pfr, port[1]); PF_MD5_UPD(pfr, neg); PF_MD5_UPD(pfr, port_op); } static void pf_hash_rule(MD5_CTX *ctx, struct pf_rule *rule) { u_int16_t x; u_int32_t y; pf_hash_rule_addr(ctx, &rule->src); pf_hash_rule_addr(ctx, &rule->dst); PF_MD5_UPD_STR(rule, label); PF_MD5_UPD_STR(rule, ifname); PF_MD5_UPD_STR(rule, match_tagname); PF_MD5_UPD_HTONS(rule, match_tag, x); /* dup? */ PF_MD5_UPD_HTONL(rule, os_fingerprint, y); PF_MD5_UPD_HTONL(rule, prob, y); PF_MD5_UPD_HTONL(rule, uid.uid[0], y); PF_MD5_UPD_HTONL(rule, uid.uid[1], y); PF_MD5_UPD(rule, uid.op); PF_MD5_UPD_HTONL(rule, gid.gid[0], y); PF_MD5_UPD_HTONL(rule, gid.gid[1], y); PF_MD5_UPD(rule, gid.op); PF_MD5_UPD_HTONL(rule, rule_flag, y); PF_MD5_UPD(rule, action); PF_MD5_UPD(rule, direction); PF_MD5_UPD(rule, af); PF_MD5_UPD(rule, quick); PF_MD5_UPD(rule, ifnot); PF_MD5_UPD(rule, match_tag_not); PF_MD5_UPD(rule, natpass); PF_MD5_UPD(rule, keep_state); PF_MD5_UPD(rule, proto); PF_MD5_UPD(rule, type); PF_MD5_UPD(rule, code); PF_MD5_UPD(rule, flags); PF_MD5_UPD(rule, flagset); PF_MD5_UPD(rule, allow_opts); PF_MD5_UPD(rule, rt); PF_MD5_UPD(rule, tos); } static int pf_commit_rules(u_int32_t ticket, int rs_num, char *anchor) { struct pf_ruleset *rs; struct pf_rule *rule, **old_array; struct pf_rulequeue *old_rules; int error; u_int32_t old_rcount; PF_RULES_WASSERT(); if (rs_num < 0 || rs_num >= PF_RULESET_MAX) return (EINVAL); rs = pf_find_ruleset(anchor); if (rs == NULL || !rs->rules[rs_num].inactive.open || ticket != rs->rules[rs_num].inactive.ticket) return (EBUSY); /* Calculate checksum for the main ruleset */ if (rs == &pf_main_ruleset) { error = pf_setup_pfsync_matching(rs); if (error != 0) return (error); } /* Swap rules, keep the old. */ old_rules = rs->rules[rs_num].active.ptr; old_rcount = rs->rules[rs_num].active.rcount; old_array = rs->rules[rs_num].active.ptr_array; rs->rules[rs_num].active.ptr = rs->rules[rs_num].inactive.ptr; rs->rules[rs_num].active.ptr_array = rs->rules[rs_num].inactive.ptr_array; rs->rules[rs_num].active.rcount = rs->rules[rs_num].inactive.rcount; rs->rules[rs_num].inactive.ptr = old_rules; rs->rules[rs_num].inactive.ptr_array = old_array; rs->rules[rs_num].inactive.rcount = old_rcount; rs->rules[rs_num].active.ticket = rs->rules[rs_num].inactive.ticket; pf_calc_skip_steps(rs->rules[rs_num].active.ptr); /* Purge the old rule list. */ while ((rule = TAILQ_FIRST(old_rules)) != NULL) pf_unlink_rule(old_rules, rule); if (rs->rules[rs_num].inactive.ptr_array) free(rs->rules[rs_num].inactive.ptr_array, M_TEMP); rs->rules[rs_num].inactive.ptr_array = NULL; rs->rules[rs_num].inactive.rcount = 0; rs->rules[rs_num].inactive.open = 0; pf_remove_if_empty_ruleset(rs); return (0); } static int pf_setup_pfsync_matching(struct pf_ruleset *rs) { MD5_CTX ctx; struct pf_rule *rule; int rs_cnt; u_int8_t digest[PF_MD5_DIGEST_LENGTH]; MD5Init(&ctx); for (rs_cnt = 0; rs_cnt < PF_RULESET_MAX; rs_cnt++) { /* XXX PF_RULESET_SCRUB as well? */ if (rs_cnt == PF_RULESET_SCRUB) continue; if (rs->rules[rs_cnt].inactive.ptr_array) free(rs->rules[rs_cnt].inactive.ptr_array, M_TEMP); rs->rules[rs_cnt].inactive.ptr_array = NULL; if (rs->rules[rs_cnt].inactive.rcount) { rs->rules[rs_cnt].inactive.ptr_array = malloc(sizeof(caddr_t) * rs->rules[rs_cnt].inactive.rcount, M_TEMP, M_NOWAIT); if (!rs->rules[rs_cnt].inactive.ptr_array) return (ENOMEM); } TAILQ_FOREACH(rule, rs->rules[rs_cnt].inactive.ptr, entries) { pf_hash_rule(&ctx, rule); (rs->rules[rs_cnt].inactive.ptr_array)[rule->nr] = rule; } } MD5Final(digest, &ctx); memcpy(V_pf_status.pf_chksum, digest, sizeof(V_pf_status.pf_chksum)); return (0); } static int pf_addr_setup(struct pf_ruleset *ruleset, struct pf_addr_wrap *addr, sa_family_t af) { int error = 0; switch (addr->type) { case PF_ADDR_TABLE: addr->p.tbl = pfr_attach_table(ruleset, addr->v.tblname); if (addr->p.tbl == NULL) error = ENOMEM; break; case PF_ADDR_DYNIFTL: error = pfi_dynaddr_setup(addr, af); break; } return (error); } static void pf_addr_copyout(struct pf_addr_wrap *addr) { switch (addr->type) { case PF_ADDR_DYNIFTL: pfi_dynaddr_copyout(addr); break; case PF_ADDR_TABLE: pf_tbladdr_copyout(addr); break; } } static int pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td) { int error = 0; /* XXX keep in sync with switch() below */ if (securelevel_gt(td->td_ucred, 2)) switch (cmd) { case DIOCGETRULES: case DIOCGETRULE: case DIOCGETADDRS: case DIOCGETADDR: case DIOCGETSTATE: case DIOCSETSTATUSIF: case DIOCGETSTATUS: case DIOCCLRSTATUS: case DIOCNATLOOK: case DIOCSETDEBUG: case DIOCGETSTATES: case DIOCGETTIMEOUT: case DIOCCLRRULECTRS: case DIOCGETLIMIT: case DIOCGETALTQS: case DIOCGETALTQ: case DIOCGETQSTATS: case DIOCGETRULESETS: case DIOCGETRULESET: case DIOCRGETTABLES: case DIOCRGETTSTATS: case DIOCRCLRTSTATS: case DIOCRCLRADDRS: case DIOCRADDADDRS: case DIOCRDELADDRS: case DIOCRSETADDRS: case DIOCRGETADDRS: case DIOCRGETASTATS: case DIOCRCLRASTATS: case DIOCRTSTADDRS: case DIOCOSFPGET: case DIOCGETSRCNODES: case DIOCCLRSRCNODES: case DIOCIGETIFACES: case DIOCGIFSPEED: case DIOCSETIFFLAG: case DIOCCLRIFFLAG: break; case DIOCRCLRTABLES: case DIOCRADDTABLES: case DIOCRDELTABLES: case DIOCRSETTFLAGS: if (((struct pfioc_table *)addr)->pfrio_flags & PFR_FLAG_DUMMY) break; /* dummy operation ok */ return (EPERM); default: return (EPERM); } if (!(flags & FWRITE)) switch (cmd) { case DIOCGETRULES: case DIOCGETADDRS: case DIOCGETADDR: case DIOCGETSTATE: case DIOCGETSTATUS: case DIOCGETSTATES: case DIOCGETTIMEOUT: case DIOCGETLIMIT: case DIOCGETALTQS: case DIOCGETALTQ: case DIOCGETQSTATS: case DIOCGETRULESETS: case DIOCGETRULESET: case DIOCNATLOOK: case DIOCRGETTABLES: case DIOCRGETTSTATS: case DIOCRGETADDRS: case DIOCRGETASTATS: case DIOCRTSTADDRS: case DIOCOSFPGET: case DIOCGETSRCNODES: case DIOCIGETIFACES: case DIOCGIFSPEED: break; case DIOCRCLRTABLES: case DIOCRADDTABLES: case DIOCRDELTABLES: case DIOCRCLRTSTATS: case DIOCRCLRADDRS: case DIOCRADDADDRS: case DIOCRDELADDRS: case DIOCRSETADDRS: case DIOCRSETTFLAGS: if (((struct pfioc_table *)addr)->pfrio_flags & PFR_FLAG_DUMMY) { flags |= FWRITE; /* need write lock for dummy */ break; /* dummy operation ok */ } return (EACCES); case DIOCGETRULE: if (((struct pfioc_rule *)addr)->action == PF_GET_CLR_CNTR) return (EACCES); break; default: return (EACCES); } CURVNET_SET(TD_TO_VNET(td)); switch (cmd) { case DIOCSTART: sx_xlock(&pf_ioctl_lock); if (V_pf_status.running) error = EEXIST; else { int cpu; error = hook_pf(); if (error) { DPFPRINTF(PF_DEBUG_MISC, ("pf: pfil registration failed\n")); break; } V_pf_status.running = 1; V_pf_status.since = time_second; CPU_FOREACH(cpu) V_pf_stateid[cpu] = time_second; DPFPRINTF(PF_DEBUG_MISC, ("pf: started\n")); } break; case DIOCSTOP: sx_xlock(&pf_ioctl_lock); if (!V_pf_status.running) error = ENOENT; else { V_pf_status.running = 0; error = dehook_pf(); if (error) { V_pf_status.running = 1; DPFPRINTF(PF_DEBUG_MISC, ("pf: pfil unregistration failed\n")); } V_pf_status.since = time_second; DPFPRINTF(PF_DEBUG_MISC, ("pf: stopped\n")); } break; case DIOCADDRULE: { struct pfioc_rule *pr = (struct pfioc_rule *)addr; struct pf_ruleset *ruleset; struct pf_rule *rule, *tail; struct pf_pooladdr *pa; struct pfi_kif *kif = NULL; int rs_num; if (pr->rule.return_icmp >> 8 > ICMP_MAXTYPE) { error = EINVAL; break; } #ifndef INET if (pr->rule.af == AF_INET) { error = EAFNOSUPPORT; break; } #endif /* INET */ #ifndef INET6 if (pr->rule.af == AF_INET6) { error = EAFNOSUPPORT; break; } #endif /* INET6 */ rule = malloc(sizeof(*rule), M_PFRULE, M_WAITOK); bcopy(&pr->rule, rule, sizeof(struct pf_rule)); if (rule->ifname[0]) kif = malloc(sizeof(*kif), PFI_MTYPE, M_WAITOK); rule->states_cur = counter_u64_alloc(M_WAITOK); rule->states_tot = counter_u64_alloc(M_WAITOK); rule->src_nodes = counter_u64_alloc(M_WAITOK); rule->cuid = td->td_ucred->cr_ruid; rule->cpid = td->td_proc ? td->td_proc->p_pid : 0; TAILQ_INIT(&rule->rpool.list); #define ERROUT(x) { error = (x); goto DIOCADDRULE_error; } PF_RULES_WLOCK(); pr->anchor[sizeof(pr->anchor) - 1] = 0; ruleset = pf_find_ruleset(pr->anchor); if (ruleset == NULL) ERROUT(EINVAL); rs_num = pf_get_ruleset_number(pr->rule.action); if (rs_num >= PF_RULESET_MAX) ERROUT(EINVAL); if (pr->ticket != ruleset->rules[rs_num].inactive.ticket) { DPFPRINTF(PF_DEBUG_MISC, ("ticket: %d != [%d]%d\n", pr->ticket, rs_num, ruleset->rules[rs_num].inactive.ticket)); ERROUT(EBUSY); } if (pr->pool_ticket != V_ticket_pabuf) { DPFPRINTF(PF_DEBUG_MISC, ("pool_ticket: %d != %d\n", pr->pool_ticket, V_ticket_pabuf)); ERROUT(EBUSY); } tail = TAILQ_LAST(ruleset->rules[rs_num].inactive.ptr, pf_rulequeue); if (tail) rule->nr = tail->nr + 1; else rule->nr = 0; if (rule->ifname[0]) { rule->kif = pfi_kif_attach(kif, rule->ifname); pfi_kif_ref(rule->kif); } else rule->kif = NULL; if (rule->rtableid > 0 && rule->rtableid >= rt_numfibs) error = EBUSY; #ifdef ALTQ /* set queue IDs */ if (rule->qname[0] != 0) { if ((rule->qid = pf_qname2qid(rule->qname)) == 0) error = EBUSY; else if (rule->pqname[0] != 0) { if ((rule->pqid = pf_qname2qid(rule->pqname)) == 0) error = EBUSY; } else rule->pqid = rule->qid; } #endif if (rule->tagname[0]) if ((rule->tag = pf_tagname2tag(rule->tagname)) == 0) error = EBUSY; if (rule->match_tagname[0]) if ((rule->match_tag = pf_tagname2tag(rule->match_tagname)) == 0) error = EBUSY; if (rule->rt && !rule->direction) error = EINVAL; if (!rule->log) rule->logif = 0; if (rule->logif >= PFLOGIFS_MAX) error = EINVAL; if (pf_addr_setup(ruleset, &rule->src.addr, rule->af)) error = ENOMEM; if (pf_addr_setup(ruleset, &rule->dst.addr, rule->af)) error = ENOMEM; if (pf_anchor_setup(rule, ruleset, pr->anchor_call)) error = EINVAL; TAILQ_FOREACH(pa, &V_pf_pabuf, entries) if (pa->addr.type == PF_ADDR_TABLE) { pa->addr.p.tbl = pfr_attach_table(ruleset, pa->addr.v.tblname); if (pa->addr.p.tbl == NULL) error = ENOMEM; } if (rule->overload_tblname[0]) { if ((rule->overload_tbl = pfr_attach_table(ruleset, rule->overload_tblname)) == NULL) error = EINVAL; else rule->overload_tbl->pfrkt_flags |= PFR_TFLAG_ACTIVE; } pf_mv_pool(&V_pf_pabuf, &rule->rpool.list); if (((((rule->action == PF_NAT) || (rule->action == PF_RDR) || (rule->action == PF_BINAT)) && rule->anchor == NULL) || (rule->rt > PF_FASTROUTE)) && (TAILQ_FIRST(&rule->rpool.list) == NULL)) error = EINVAL; if (error) { pf_free_rule(rule); PF_RULES_WUNLOCK(); break; } rule->rpool.cur = TAILQ_FIRST(&rule->rpool.list); rule->evaluations = rule->packets[0] = rule->packets[1] = rule->bytes[0] = rule->bytes[1] = 0; TAILQ_INSERT_TAIL(ruleset->rules[rs_num].inactive.ptr, rule, entries); ruleset->rules[rs_num].inactive.rcount++; PF_RULES_WUNLOCK(); break; #undef ERROUT DIOCADDRULE_error: PF_RULES_WUNLOCK(); counter_u64_free(rule->states_cur); counter_u64_free(rule->states_tot); counter_u64_free(rule->src_nodes); free(rule, M_PFRULE); if (kif) free(kif, PFI_MTYPE); break; } case DIOCGETRULES: { struct pfioc_rule *pr = (struct pfioc_rule *)addr; struct pf_ruleset *ruleset; struct pf_rule *tail; int rs_num; PF_RULES_WLOCK(); pr->anchor[sizeof(pr->anchor) - 1] = 0; ruleset = pf_find_ruleset(pr->anchor); if (ruleset == NULL) { PF_RULES_WUNLOCK(); error = EINVAL; break; } rs_num = pf_get_ruleset_number(pr->rule.action); if (rs_num >= PF_RULESET_MAX) { PF_RULES_WUNLOCK(); error = EINVAL; break; } tail = TAILQ_LAST(ruleset->rules[rs_num].active.ptr, pf_rulequeue); if (tail) pr->nr = tail->nr + 1; else pr->nr = 0; pr->ticket = ruleset->rules[rs_num].active.ticket; PF_RULES_WUNLOCK(); break; } case DIOCGETRULE: { struct pfioc_rule *pr = (struct pfioc_rule *)addr; struct pf_ruleset *ruleset; struct pf_rule *rule; int rs_num, i; PF_RULES_WLOCK(); pr->anchor[sizeof(pr->anchor) - 1] = 0; ruleset = pf_find_ruleset(pr->anchor); if (ruleset == NULL) { PF_RULES_WUNLOCK(); error = EINVAL; break; } rs_num = pf_get_ruleset_number(pr->rule.action); if (rs_num >= PF_RULESET_MAX) { PF_RULES_WUNLOCK(); error = EINVAL; break; } if (pr->ticket != ruleset->rules[rs_num].active.ticket) { PF_RULES_WUNLOCK(); error = EBUSY; break; } rule = TAILQ_FIRST(ruleset->rules[rs_num].active.ptr); while ((rule != NULL) && (rule->nr != pr->nr)) rule = TAILQ_NEXT(rule, entries); if (rule == NULL) { PF_RULES_WUNLOCK(); error = EBUSY; break; } bcopy(rule, &pr->rule, sizeof(struct pf_rule)); pr->rule.u_states_cur = counter_u64_fetch(rule->states_cur); pr->rule.u_states_tot = counter_u64_fetch(rule->states_tot); pr->rule.u_src_nodes = counter_u64_fetch(rule->src_nodes); if (pf_anchor_copyout(ruleset, rule, pr)) { PF_RULES_WUNLOCK(); error = EBUSY; break; } pf_addr_copyout(&pr->rule.src.addr); pf_addr_copyout(&pr->rule.dst.addr); for (i = 0; i < PF_SKIP_COUNT; ++i) if (rule->skip[i].ptr == NULL) pr->rule.skip[i].nr = -1; else pr->rule.skip[i].nr = rule->skip[i].ptr->nr; if (pr->action == PF_GET_CLR_CNTR) { rule->evaluations = 0; rule->packets[0] = rule->packets[1] = 0; rule->bytes[0] = rule->bytes[1] = 0; counter_u64_zero(rule->states_tot); } PF_RULES_WUNLOCK(); break; } case DIOCCHANGERULE: { struct pfioc_rule *pcr = (struct pfioc_rule *)addr; struct pf_ruleset *ruleset; struct pf_rule *oldrule = NULL, *newrule = NULL; struct pfi_kif *kif = NULL; struct pf_pooladdr *pa; u_int32_t nr = 0; int rs_num; if (pcr->action < PF_CHANGE_ADD_HEAD || pcr->action > PF_CHANGE_GET_TICKET) { error = EINVAL; break; } if (pcr->rule.return_icmp >> 8 > ICMP_MAXTYPE) { error = EINVAL; break; } if (pcr->action != PF_CHANGE_REMOVE) { #ifndef INET if (pcr->rule.af == AF_INET) { error = EAFNOSUPPORT; break; } #endif /* INET */ #ifndef INET6 if (pcr->rule.af == AF_INET6) { error = EAFNOSUPPORT; break; } #endif /* INET6 */ newrule = malloc(sizeof(*newrule), M_PFRULE, M_WAITOK); bcopy(&pcr->rule, newrule, sizeof(struct pf_rule)); if (newrule->ifname[0]) kif = malloc(sizeof(*kif), PFI_MTYPE, M_WAITOK); newrule->states_cur = counter_u64_alloc(M_WAITOK); newrule->states_tot = counter_u64_alloc(M_WAITOK); newrule->src_nodes = counter_u64_alloc(M_WAITOK); newrule->cuid = td->td_ucred->cr_ruid; newrule->cpid = td->td_proc ? td->td_proc->p_pid : 0; TAILQ_INIT(&newrule->rpool.list); } #define ERROUT(x) { error = (x); goto DIOCCHANGERULE_error; } PF_RULES_WLOCK(); if (!(pcr->action == PF_CHANGE_REMOVE || pcr->action == PF_CHANGE_GET_TICKET) && pcr->pool_ticket != V_ticket_pabuf) ERROUT(EBUSY); ruleset = pf_find_ruleset(pcr->anchor); if (ruleset == NULL) ERROUT(EINVAL); rs_num = pf_get_ruleset_number(pcr->rule.action); if (rs_num >= PF_RULESET_MAX) ERROUT(EINVAL); if (pcr->action == PF_CHANGE_GET_TICKET) { pcr->ticket = ++ruleset->rules[rs_num].active.ticket; ERROUT(0); } else if (pcr->ticket != ruleset->rules[rs_num].active.ticket) ERROUT(EINVAL); if (pcr->action != PF_CHANGE_REMOVE) { if (newrule->ifname[0]) { newrule->kif = pfi_kif_attach(kif, newrule->ifname); pfi_kif_ref(newrule->kif); } else newrule->kif = NULL; if (newrule->rtableid > 0 && newrule->rtableid >= rt_numfibs) error = EBUSY; #ifdef ALTQ /* set queue IDs */ if (newrule->qname[0] != 0) { if ((newrule->qid = pf_qname2qid(newrule->qname)) == 0) error = EBUSY; else if (newrule->pqname[0] != 0) { if ((newrule->pqid = pf_qname2qid(newrule->pqname)) == 0) error = EBUSY; } else newrule->pqid = newrule->qid; } #endif /* ALTQ */ if (newrule->tagname[0]) if ((newrule->tag = pf_tagname2tag(newrule->tagname)) == 0) error = EBUSY; if (newrule->match_tagname[0]) if ((newrule->match_tag = pf_tagname2tag( newrule->match_tagname)) == 0) error = EBUSY; if (newrule->rt && !newrule->direction) error = EINVAL; if (!newrule->log) newrule->logif = 0; if (newrule->logif >= PFLOGIFS_MAX) error = EINVAL; if (pf_addr_setup(ruleset, &newrule->src.addr, newrule->af)) error = ENOMEM; if (pf_addr_setup(ruleset, &newrule->dst.addr, newrule->af)) error = ENOMEM; if (pf_anchor_setup(newrule, ruleset, pcr->anchor_call)) error = EINVAL; TAILQ_FOREACH(pa, &V_pf_pabuf, entries) if (pa->addr.type == PF_ADDR_TABLE) { pa->addr.p.tbl = pfr_attach_table(ruleset, pa->addr.v.tblname); if (pa->addr.p.tbl == NULL) error = ENOMEM; } if (newrule->overload_tblname[0]) { if ((newrule->overload_tbl = pfr_attach_table( ruleset, newrule->overload_tblname)) == NULL) error = EINVAL; else newrule->overload_tbl->pfrkt_flags |= PFR_TFLAG_ACTIVE; } pf_mv_pool(&V_pf_pabuf, &newrule->rpool.list); if (((((newrule->action == PF_NAT) || (newrule->action == PF_RDR) || (newrule->action == PF_BINAT) || (newrule->rt > PF_FASTROUTE)) && !newrule->anchor)) && (TAILQ_FIRST(&newrule->rpool.list) == NULL)) error = EINVAL; if (error) { pf_free_rule(newrule); PF_RULES_WUNLOCK(); break; } newrule->rpool.cur = TAILQ_FIRST(&newrule->rpool.list); newrule->evaluations = 0; newrule->packets[0] = newrule->packets[1] = 0; newrule->bytes[0] = newrule->bytes[1] = 0; } pf_empty_pool(&V_pf_pabuf); if (pcr->action == PF_CHANGE_ADD_HEAD) oldrule = TAILQ_FIRST( ruleset->rules[rs_num].active.ptr); else if (pcr->action == PF_CHANGE_ADD_TAIL) oldrule = TAILQ_LAST( ruleset->rules[rs_num].active.ptr, pf_rulequeue); else { oldrule = TAILQ_FIRST( ruleset->rules[rs_num].active.ptr); while ((oldrule != NULL) && (oldrule->nr != pcr->nr)) oldrule = TAILQ_NEXT(oldrule, entries); if (oldrule == NULL) { if (newrule != NULL) pf_free_rule(newrule); PF_RULES_WUNLOCK(); error = EINVAL; break; } } if (pcr->action == PF_CHANGE_REMOVE) { pf_unlink_rule(ruleset->rules[rs_num].active.ptr, oldrule); ruleset->rules[rs_num].active.rcount--; } else { if (oldrule == NULL) TAILQ_INSERT_TAIL( ruleset->rules[rs_num].active.ptr, newrule, entries); else if (pcr->action == PF_CHANGE_ADD_HEAD || pcr->action == PF_CHANGE_ADD_BEFORE) TAILQ_INSERT_BEFORE(oldrule, newrule, entries); else TAILQ_INSERT_AFTER( ruleset->rules[rs_num].active.ptr, oldrule, newrule, entries); ruleset->rules[rs_num].active.rcount++; } nr = 0; TAILQ_FOREACH(oldrule, ruleset->rules[rs_num].active.ptr, entries) oldrule->nr = nr++; ruleset->rules[rs_num].active.ticket++; pf_calc_skip_steps(ruleset->rules[rs_num].active.ptr); pf_remove_if_empty_ruleset(ruleset); PF_RULES_WUNLOCK(); break; #undef ERROUT DIOCCHANGERULE_error: PF_RULES_WUNLOCK(); if (newrule != NULL) { counter_u64_free(newrule->states_cur); counter_u64_free(newrule->states_tot); counter_u64_free(newrule->src_nodes); free(newrule, M_PFRULE); } if (kif != NULL) free(kif, PFI_MTYPE); break; } case DIOCCLRSTATES: { struct pf_state *s; struct pfioc_state_kill *psk = (struct pfioc_state_kill *)addr; u_int i, killed = 0; for (i = 0; i <= pf_hashmask; i++) { struct pf_idhash *ih = &V_pf_idhash[i]; relock_DIOCCLRSTATES: PF_HASHROW_LOCK(ih); LIST_FOREACH(s, &ih->states, entry) if (!psk->psk_ifname[0] || !strcmp(psk->psk_ifname, s->kif->pfik_name)) { /* * Don't send out individual * delete messages. */ s->state_flags |= PFSTATE_NOSYNC; pf_unlink_state(s, PF_ENTER_LOCKED); killed++; goto relock_DIOCCLRSTATES; } PF_HASHROW_UNLOCK(ih); } psk->psk_killed = killed; if (pfsync_clear_states_ptr != NULL) pfsync_clear_states_ptr(V_pf_status.hostid, psk->psk_ifname); break; } case DIOCKILLSTATES: { struct pf_state *s; struct pf_state_key *sk; struct pf_addr *srcaddr, *dstaddr; u_int16_t srcport, dstport; struct pfioc_state_kill *psk = (struct pfioc_state_kill *)addr; u_int i, killed = 0; if (psk->psk_pfcmp.id) { if (psk->psk_pfcmp.creatorid == 0) psk->psk_pfcmp.creatorid = V_pf_status.hostid; if ((s = pf_find_state_byid(psk->psk_pfcmp.id, psk->psk_pfcmp.creatorid))) { pf_unlink_state(s, PF_ENTER_LOCKED); psk->psk_killed = 1; } break; } for (i = 0; i <= pf_hashmask; i++) { struct pf_idhash *ih = &V_pf_idhash[i]; relock_DIOCKILLSTATES: PF_HASHROW_LOCK(ih); LIST_FOREACH(s, &ih->states, entry) { sk = s->key[PF_SK_WIRE]; if (s->direction == PF_OUT) { srcaddr = &sk->addr[1]; dstaddr = &sk->addr[0]; srcport = sk->port[0]; dstport = sk->port[0]; } else { srcaddr = &sk->addr[0]; dstaddr = &sk->addr[1]; srcport = sk->port[0]; dstport = sk->port[0]; } if ((!psk->psk_af || sk->af == psk->psk_af) && (!psk->psk_proto || psk->psk_proto == sk->proto) && PF_MATCHA(psk->psk_src.neg, &psk->psk_src.addr.v.a.addr, &psk->psk_src.addr.v.a.mask, srcaddr, sk->af) && PF_MATCHA(psk->psk_dst.neg, &psk->psk_dst.addr.v.a.addr, &psk->psk_dst.addr.v.a.mask, dstaddr, sk->af) && (psk->psk_src.port_op == 0 || pf_match_port(psk->psk_src.port_op, psk->psk_src.port[0], psk->psk_src.port[1], srcport)) && (psk->psk_dst.port_op == 0 || pf_match_port(psk->psk_dst.port_op, psk->psk_dst.port[0], psk->psk_dst.port[1], dstport)) && (!psk->psk_label[0] || (s->rule.ptr->label[0] && !strcmp(psk->psk_label, s->rule.ptr->label))) && (!psk->psk_ifname[0] || !strcmp(psk->psk_ifname, s->kif->pfik_name))) { pf_unlink_state(s, PF_ENTER_LOCKED); killed++; goto relock_DIOCKILLSTATES; } } PF_HASHROW_UNLOCK(ih); } psk->psk_killed = killed; break; } case DIOCADDSTATE: { struct pfioc_state *ps = (struct pfioc_state *)addr; struct pfsync_state *sp = &ps->state; if (sp->timeout >= PFTM_MAX) { error = EINVAL; break; } if (pfsync_state_import_ptr != NULL) { PF_RULES_RLOCK(); error = pfsync_state_import_ptr(sp, PFSYNC_SI_IOCTL); PF_RULES_RUNLOCK(); } else error = EOPNOTSUPP; break; } case DIOCGETSTATE: { struct pfioc_state *ps = (struct pfioc_state *)addr; struct pf_state *s; s = pf_find_state_byid(ps->state.id, ps->state.creatorid); if (s == NULL) { error = ENOENT; break; } pfsync_state_export(&ps->state, s); PF_STATE_UNLOCK(s); break; } case DIOCGETSTATES: { struct pfioc_states *ps = (struct pfioc_states *)addr; struct pf_state *s; struct pfsync_state *pstore, *p; int i, nr; if (ps->ps_len == 0) { nr = uma_zone_get_cur(V_pf_state_z); ps->ps_len = sizeof(struct pfsync_state) * nr; break; } p = pstore = malloc(ps->ps_len, M_TEMP, M_WAITOK); nr = 0; for (i = 0; i <= pf_hashmask; i++) { struct pf_idhash *ih = &V_pf_idhash[i]; PF_HASHROW_LOCK(ih); LIST_FOREACH(s, &ih->states, entry) { if (s->timeout == PFTM_UNLINKED) continue; if ((nr+1) * sizeof(*p) > ps->ps_len) { PF_HASHROW_UNLOCK(ih); goto DIOCGETSTATES_full; } pfsync_state_export(p, s); p++; nr++; } PF_HASHROW_UNLOCK(ih); } DIOCGETSTATES_full: error = copyout(pstore, ps->ps_states, sizeof(struct pfsync_state) * nr); if (error) { free(pstore, M_TEMP); break; } ps->ps_len = sizeof(struct pfsync_state) * nr; free(pstore, M_TEMP); break; } case DIOCGETSTATUS: { struct pf_status *s = (struct pf_status *)addr; PF_RULES_RLOCK(); s->running = V_pf_status.running; s->since = V_pf_status.since; s->debug = V_pf_status.debug; s->hostid = V_pf_status.hostid; s->states = V_pf_status.states; s->src_nodes = V_pf_status.src_nodes; for (int i = 0; i < PFRES_MAX; i++) s->counters[i] = counter_u64_fetch(V_pf_status.counters[i]); for (int i = 0; i < LCNT_MAX; i++) s->lcounters[i] = counter_u64_fetch(V_pf_status.lcounters[i]); for (int i = 0; i < FCNT_MAX; i++) s->fcounters[i] = counter_u64_fetch(V_pf_status.fcounters[i]); for (int i = 0; i < SCNT_MAX; i++) s->scounters[i] = counter_u64_fetch(V_pf_status.scounters[i]); bcopy(V_pf_status.ifname, s->ifname, IFNAMSIZ); bcopy(V_pf_status.pf_chksum, s->pf_chksum, PF_MD5_DIGEST_LENGTH); pfi_update_status(s->ifname, s); PF_RULES_RUNLOCK(); break; } case DIOCSETSTATUSIF: { struct pfioc_if *pi = (struct pfioc_if *)addr; if (pi->ifname[0] == 0) { bzero(V_pf_status.ifname, IFNAMSIZ); break; } PF_RULES_WLOCK(); strlcpy(V_pf_status.ifname, pi->ifname, IFNAMSIZ); PF_RULES_WUNLOCK(); break; } case DIOCCLRSTATUS: { PF_RULES_WLOCK(); for (int i = 0; i < PFRES_MAX; i++) counter_u64_zero(V_pf_status.counters[i]); for (int i = 0; i < FCNT_MAX; i++) counter_u64_zero(V_pf_status.fcounters[i]); for (int i = 0; i < SCNT_MAX; i++) counter_u64_zero(V_pf_status.scounters[i]); V_pf_status.since = time_second; if (*V_pf_status.ifname) pfi_update_status(V_pf_status.ifname, NULL); PF_RULES_WUNLOCK(); break; } case DIOCNATLOOK: { struct pfioc_natlook *pnl = (struct pfioc_natlook *)addr; struct pf_state_key *sk; struct pf_state *state; struct pf_state_key_cmp key; int m = 0, direction = pnl->direction; int sidx, didx; /* NATLOOK src and dst are reversed, so reverse sidx/didx */ sidx = (direction == PF_IN) ? 1 : 0; didx = (direction == PF_IN) ? 0 : 1; if (!pnl->proto || PF_AZERO(&pnl->saddr, pnl->af) || PF_AZERO(&pnl->daddr, pnl->af) || ((pnl->proto == IPPROTO_TCP || pnl->proto == IPPROTO_UDP) && (!pnl->dport || !pnl->sport))) error = EINVAL; else { bzero(&key, sizeof(key)); key.af = pnl->af; key.proto = pnl->proto; PF_ACPY(&key.addr[sidx], &pnl->saddr, pnl->af); key.port[sidx] = pnl->sport; PF_ACPY(&key.addr[didx], &pnl->daddr, pnl->af); key.port[didx] = pnl->dport; state = pf_find_state_all(&key, direction, &m); if (m > 1) error = E2BIG; /* more than one state */ else if (state != NULL) { /* XXXGL: not locked read */ sk = state->key[sidx]; PF_ACPY(&pnl->rsaddr, &sk->addr[sidx], sk->af); pnl->rsport = sk->port[sidx]; PF_ACPY(&pnl->rdaddr, &sk->addr[didx], sk->af); pnl->rdport = sk->port[didx]; } else error = ENOENT; } break; } case DIOCSETTIMEOUT: { struct pfioc_tm *pt = (struct pfioc_tm *)addr; int old; if (pt->timeout < 0 || pt->timeout >= PFTM_MAX || pt->seconds < 0) { error = EINVAL; break; } PF_RULES_WLOCK(); old = V_pf_default_rule.timeout[pt->timeout]; if (pt->timeout == PFTM_INTERVAL && pt->seconds == 0) pt->seconds = 1; V_pf_default_rule.timeout[pt->timeout] = pt->seconds; if (pt->timeout == PFTM_INTERVAL && pt->seconds < old) wakeup(pf_purge_thread); pt->seconds = old; PF_RULES_WUNLOCK(); break; } case DIOCGETTIMEOUT: { struct pfioc_tm *pt = (struct pfioc_tm *)addr; if (pt->timeout < 0 || pt->timeout >= PFTM_MAX) { error = EINVAL; break; } PF_RULES_RLOCK(); pt->seconds = V_pf_default_rule.timeout[pt->timeout]; PF_RULES_RUNLOCK(); break; } case DIOCGETLIMIT: { struct pfioc_limit *pl = (struct pfioc_limit *)addr; if (pl->index < 0 || pl->index >= PF_LIMIT_MAX) { error = EINVAL; break; } PF_RULES_RLOCK(); pl->limit = V_pf_limits[pl->index].limit; PF_RULES_RUNLOCK(); break; } case DIOCSETLIMIT: { struct pfioc_limit *pl = (struct pfioc_limit *)addr; int old_limit; PF_RULES_WLOCK(); if (pl->index < 0 || pl->index >= PF_LIMIT_MAX || V_pf_limits[pl->index].zone == NULL) { PF_RULES_WUNLOCK(); error = EINVAL; break; } uma_zone_set_max(V_pf_limits[pl->index].zone, pl->limit); old_limit = V_pf_limits[pl->index].limit; V_pf_limits[pl->index].limit = pl->limit; pl->limit = old_limit; PF_RULES_WUNLOCK(); break; } case DIOCSETDEBUG: { u_int32_t *level = (u_int32_t *)addr; PF_RULES_WLOCK(); V_pf_status.debug = *level; PF_RULES_WUNLOCK(); break; } case DIOCCLRRULECTRS: { /* obsoleted by DIOCGETRULE with action=PF_GET_CLR_CNTR */ struct pf_ruleset *ruleset = &pf_main_ruleset; struct pf_rule *rule; PF_RULES_WLOCK(); TAILQ_FOREACH(rule, ruleset->rules[PF_RULESET_FILTER].active.ptr, entries) { rule->evaluations = 0; rule->packets[0] = rule->packets[1] = 0; rule->bytes[0] = rule->bytes[1] = 0; } PF_RULES_WUNLOCK(); break; } case DIOCGIFSPEED: { struct pf_ifspeed *psp = (struct pf_ifspeed *)addr; struct pf_ifspeed ps; struct ifnet *ifp; if (psp->ifname[0] != 0) { /* Can we completely trust user-land? */ strlcpy(ps.ifname, psp->ifname, IFNAMSIZ); ifp = ifunit(ps.ifname); if (ifp != NULL) psp->baudrate = ifp->if_baudrate; else error = EINVAL; } else error = EINVAL; break; } #ifdef ALTQ case DIOCSTARTALTQ: { struct pf_altq *altq; PF_RULES_WLOCK(); /* enable all altq interfaces on active list */ TAILQ_FOREACH(altq, V_pf_altqs_active, entries) { if (altq->qname[0] == 0 && (altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { error = pf_enable_altq(altq); if (error != 0) break; } } if (error == 0) V_pf_altq_running = 1; PF_RULES_WUNLOCK(); DPFPRINTF(PF_DEBUG_MISC, ("altq: started\n")); break; } case DIOCSTOPALTQ: { struct pf_altq *altq; PF_RULES_WLOCK(); /* disable all altq interfaces on active list */ TAILQ_FOREACH(altq, V_pf_altqs_active, entries) { if (altq->qname[0] == 0 && (altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { error = pf_disable_altq(altq); if (error != 0) break; } } if (error == 0) V_pf_altq_running = 0; PF_RULES_WUNLOCK(); DPFPRINTF(PF_DEBUG_MISC, ("altq: stopped\n")); break; } case DIOCADDALTQ: { struct pfioc_altq *pa = (struct pfioc_altq *)addr; struct pf_altq *altq, *a; struct ifnet *ifp; altq = malloc(sizeof(*altq), M_PFALTQ, M_WAITOK); bcopy(&pa->altq, altq, sizeof(struct pf_altq)); altq->local_flags = 0; PF_RULES_WLOCK(); if (pa->ticket != V_ticket_altqs_inactive) { PF_RULES_WUNLOCK(); free(altq, M_PFALTQ); error = EBUSY; break; } /* * if this is for a queue, find the discipline and * copy the necessary fields */ if (altq->qname[0] != 0) { if ((altq->qid = pf_qname2qid(altq->qname)) == 0) { PF_RULES_WUNLOCK(); error = EBUSY; free(altq, M_PFALTQ); break; } altq->altq_disc = NULL; TAILQ_FOREACH(a, V_pf_altqs_inactive, entries) { if (strncmp(a->ifname, altq->ifname, IFNAMSIZ) == 0 && a->qname[0] == 0) { altq->altq_disc = a->altq_disc; break; } } } if ((ifp = ifunit(altq->ifname)) == NULL) altq->local_flags |= PFALTQ_FLAG_IF_REMOVED; else error = altq_add(altq); if (error) { PF_RULES_WUNLOCK(); free(altq, M_PFALTQ); break; } TAILQ_INSERT_TAIL(V_pf_altqs_inactive, altq, entries); bcopy(altq, &pa->altq, sizeof(struct pf_altq)); PF_RULES_WUNLOCK(); break; } case DIOCGETALTQS: { struct pfioc_altq *pa = (struct pfioc_altq *)addr; struct pf_altq *altq; PF_RULES_RLOCK(); pa->nr = 0; TAILQ_FOREACH(altq, V_pf_altqs_active, entries) pa->nr++; pa->ticket = V_ticket_altqs_active; PF_RULES_RUNLOCK(); break; } case DIOCGETALTQ: { struct pfioc_altq *pa = (struct pfioc_altq *)addr; struct pf_altq *altq; u_int32_t nr; PF_RULES_RLOCK(); if (pa->ticket != V_ticket_altqs_active) { PF_RULES_RUNLOCK(); error = EBUSY; break; } nr = 0; altq = TAILQ_FIRST(V_pf_altqs_active); while ((altq != NULL) && (nr < pa->nr)) { altq = TAILQ_NEXT(altq, entries); nr++; } if (altq == NULL) { PF_RULES_RUNLOCK(); error = EBUSY; break; } bcopy(altq, &pa->altq, sizeof(struct pf_altq)); PF_RULES_RUNLOCK(); break; } case DIOCCHANGEALTQ: /* CHANGEALTQ not supported yet! */ error = ENODEV; break; case DIOCGETQSTATS: { struct pfioc_qstats *pq = (struct pfioc_qstats *)addr; struct pf_altq *altq; u_int32_t nr; int nbytes; PF_RULES_RLOCK(); if (pq->ticket != V_ticket_altqs_active) { PF_RULES_RUNLOCK(); error = EBUSY; break; } nbytes = pq->nbytes; nr = 0; altq = TAILQ_FIRST(V_pf_altqs_active); while ((altq != NULL) && (nr < pq->nr)) { altq = TAILQ_NEXT(altq, entries); nr++; } if (altq == NULL) { PF_RULES_RUNLOCK(); error = EBUSY; break; } if ((altq->local_flags & PFALTQ_FLAG_IF_REMOVED) != 0) { PF_RULES_RUNLOCK(); error = ENXIO; break; } PF_RULES_RUNLOCK(); error = altq_getqstats(altq, pq->buf, &nbytes); if (error == 0) { pq->scheduler = altq->scheduler; pq->nbytes = nbytes; } break; } #endif /* ALTQ */ case DIOCBEGINADDRS: { struct pfioc_pooladdr *pp = (struct pfioc_pooladdr *)addr; PF_RULES_WLOCK(); pf_empty_pool(&V_pf_pabuf); pp->ticket = ++V_ticket_pabuf; PF_RULES_WUNLOCK(); break; } case DIOCADDADDR: { struct pfioc_pooladdr *pp = (struct pfioc_pooladdr *)addr; struct pf_pooladdr *pa; struct pfi_kif *kif = NULL; #ifndef INET if (pp->af == AF_INET) { error = EAFNOSUPPORT; break; } #endif /* INET */ #ifndef INET6 if (pp->af == AF_INET6) { error = EAFNOSUPPORT; break; } #endif /* INET6 */ if (pp->addr.addr.type != PF_ADDR_ADDRMASK && pp->addr.addr.type != PF_ADDR_DYNIFTL && pp->addr.addr.type != PF_ADDR_TABLE) { error = EINVAL; break; } pa = malloc(sizeof(*pa), M_PFRULE, M_WAITOK); bcopy(&pp->addr, pa, sizeof(struct pf_pooladdr)); if (pa->ifname[0]) kif = malloc(sizeof(*kif), PFI_MTYPE, M_WAITOK); PF_RULES_WLOCK(); if (pp->ticket != V_ticket_pabuf) { PF_RULES_WUNLOCK(); if (pa->ifname[0]) free(kif, PFI_MTYPE); free(pa, M_PFRULE); error = EBUSY; break; } if (pa->ifname[0]) { pa->kif = pfi_kif_attach(kif, pa->ifname); pfi_kif_ref(pa->kif); } else pa->kif = NULL; if (pa->addr.type == PF_ADDR_DYNIFTL && ((error = pfi_dynaddr_setup(&pa->addr, pp->af)) != 0)) { if (pa->ifname[0]) pfi_kif_unref(pa->kif); PF_RULES_WUNLOCK(); free(pa, M_PFRULE); break; } TAILQ_INSERT_TAIL(&V_pf_pabuf, pa, entries); PF_RULES_WUNLOCK(); break; } case DIOCGETADDRS: { struct pfioc_pooladdr *pp = (struct pfioc_pooladdr *)addr; struct pf_pool *pool; struct pf_pooladdr *pa; PF_RULES_RLOCK(); pp->nr = 0; pool = pf_get_pool(pp->anchor, pp->ticket, pp->r_action, pp->r_num, 0, 1, 0); if (pool == NULL) { PF_RULES_RUNLOCK(); error = EBUSY; break; } TAILQ_FOREACH(pa, &pool->list, entries) pp->nr++; PF_RULES_RUNLOCK(); break; } case DIOCGETADDR: { struct pfioc_pooladdr *pp = (struct pfioc_pooladdr *)addr; struct pf_pool *pool; struct pf_pooladdr *pa; u_int32_t nr = 0; PF_RULES_RLOCK(); pool = pf_get_pool(pp->anchor, pp->ticket, pp->r_action, pp->r_num, 0, 1, 1); if (pool == NULL) { PF_RULES_RUNLOCK(); error = EBUSY; break; } pa = TAILQ_FIRST(&pool->list); while ((pa != NULL) && (nr < pp->nr)) { pa = TAILQ_NEXT(pa, entries); nr++; } if (pa == NULL) { PF_RULES_RUNLOCK(); error = EBUSY; break; } bcopy(pa, &pp->addr, sizeof(struct pf_pooladdr)); pf_addr_copyout(&pp->addr.addr); PF_RULES_RUNLOCK(); break; } case DIOCCHANGEADDR: { struct pfioc_pooladdr *pca = (struct pfioc_pooladdr *)addr; struct pf_pool *pool; struct pf_pooladdr *oldpa = NULL, *newpa = NULL; struct pf_ruleset *ruleset; struct pfi_kif *kif = NULL; if (pca->action < PF_CHANGE_ADD_HEAD || pca->action > PF_CHANGE_REMOVE) { error = EINVAL; break; } if (pca->addr.addr.type != PF_ADDR_ADDRMASK && pca->addr.addr.type != PF_ADDR_DYNIFTL && pca->addr.addr.type != PF_ADDR_TABLE) { error = EINVAL; break; } if (pca->action != PF_CHANGE_REMOVE) { #ifndef INET if (pca->af == AF_INET) { error = EAFNOSUPPORT; break; } #endif /* INET */ #ifndef INET6 if (pca->af == AF_INET6) { error = EAFNOSUPPORT; break; } #endif /* INET6 */ newpa = malloc(sizeof(*newpa), M_PFRULE, M_WAITOK); bcopy(&pca->addr, newpa, sizeof(struct pf_pooladdr)); if (newpa->ifname[0]) kif = malloc(sizeof(*kif), PFI_MTYPE, M_WAITOK); newpa->kif = NULL; } #define ERROUT(x) { error = (x); goto DIOCCHANGEADDR_error; } PF_RULES_WLOCK(); ruleset = pf_find_ruleset(pca->anchor); if (ruleset == NULL) ERROUT(EBUSY); pool = pf_get_pool(pca->anchor, pca->ticket, pca->r_action, pca->r_num, pca->r_last, 1, 1); if (pool == NULL) ERROUT(EBUSY); if (pca->action != PF_CHANGE_REMOVE) { if (newpa->ifname[0]) { newpa->kif = pfi_kif_attach(kif, newpa->ifname); pfi_kif_ref(newpa->kif); kif = NULL; } switch (newpa->addr.type) { case PF_ADDR_DYNIFTL: error = pfi_dynaddr_setup(&newpa->addr, pca->af); break; case PF_ADDR_TABLE: newpa->addr.p.tbl = pfr_attach_table(ruleset, newpa->addr.v.tblname); if (newpa->addr.p.tbl == NULL) error = ENOMEM; break; } if (error) goto DIOCCHANGEADDR_error; } switch (pca->action) { case PF_CHANGE_ADD_HEAD: oldpa = TAILQ_FIRST(&pool->list); break; case PF_CHANGE_ADD_TAIL: oldpa = TAILQ_LAST(&pool->list, pf_palist); break; default: oldpa = TAILQ_FIRST(&pool->list); for (int i = 0; oldpa && i < pca->nr; i++) oldpa = TAILQ_NEXT(oldpa, entries); if (oldpa == NULL) ERROUT(EINVAL); } if (pca->action == PF_CHANGE_REMOVE) { TAILQ_REMOVE(&pool->list, oldpa, entries); switch (oldpa->addr.type) { case PF_ADDR_DYNIFTL: pfi_dynaddr_remove(oldpa->addr.p.dyn); break; case PF_ADDR_TABLE: pfr_detach_table(oldpa->addr.p.tbl); break; } if (oldpa->kif) pfi_kif_unref(oldpa->kif); free(oldpa, M_PFRULE); } else { if (oldpa == NULL) TAILQ_INSERT_TAIL(&pool->list, newpa, entries); else if (pca->action == PF_CHANGE_ADD_HEAD || pca->action == PF_CHANGE_ADD_BEFORE) TAILQ_INSERT_BEFORE(oldpa, newpa, entries); else TAILQ_INSERT_AFTER(&pool->list, oldpa, newpa, entries); } pool->cur = TAILQ_FIRST(&pool->list); PF_ACPY(&pool->counter, &pool->cur->addr.v.a.addr, pca->af); PF_RULES_WUNLOCK(); break; #undef ERROUT DIOCCHANGEADDR_error: if (newpa->kif) pfi_kif_unref(newpa->kif); PF_RULES_WUNLOCK(); if (newpa != NULL) free(newpa, M_PFRULE); if (kif != NULL) free(kif, PFI_MTYPE); break; } case DIOCGETRULESETS: { struct pfioc_ruleset *pr = (struct pfioc_ruleset *)addr; struct pf_ruleset *ruleset; struct pf_anchor *anchor; PF_RULES_RLOCK(); pr->path[sizeof(pr->path) - 1] = 0; if ((ruleset = pf_find_ruleset(pr->path)) == NULL) { PF_RULES_RUNLOCK(); error = ENOENT; break; } pr->nr = 0; if (ruleset->anchor == NULL) { /* XXX kludge for pf_main_ruleset */ RB_FOREACH(anchor, pf_anchor_global, &V_pf_anchors) if (anchor->parent == NULL) pr->nr++; } else { RB_FOREACH(anchor, pf_anchor_node, &ruleset->anchor->children) pr->nr++; } PF_RULES_RUNLOCK(); break; } case DIOCGETRULESET: { struct pfioc_ruleset *pr = (struct pfioc_ruleset *)addr; struct pf_ruleset *ruleset; struct pf_anchor *anchor; u_int32_t nr = 0; PF_RULES_RLOCK(); pr->path[sizeof(pr->path) - 1] = 0; if ((ruleset = pf_find_ruleset(pr->path)) == NULL) { PF_RULES_RUNLOCK(); error = ENOENT; break; } pr->name[0] = 0; if (ruleset->anchor == NULL) { /* XXX kludge for pf_main_ruleset */ RB_FOREACH(anchor, pf_anchor_global, &V_pf_anchors) if (anchor->parent == NULL && nr++ == pr->nr) { strlcpy(pr->name, anchor->name, sizeof(pr->name)); break; } } else { RB_FOREACH(anchor, pf_anchor_node, &ruleset->anchor->children) if (nr++ == pr->nr) { strlcpy(pr->name, anchor->name, sizeof(pr->name)); break; } } if (!pr->name[0]) error = EBUSY; PF_RULES_RUNLOCK(); break; } case DIOCRCLRTABLES: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != 0) { error = ENODEV; break; } PF_RULES_WLOCK(); error = pfr_clr_tables(&io->pfrio_table, &io->pfrio_ndel, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); break; } case DIOCRADDTABLES: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_table *pfrts; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_table)) { error = ENODEV; break; } totlen = io->pfrio_size * sizeof(struct pfr_table); pfrts = malloc(totlen, M_TEMP, M_WAITOK); error = copyin(io->pfrio_buffer, pfrts, totlen); if (error) { free(pfrts, M_TEMP); break; } PF_RULES_WLOCK(); error = pfr_add_tables(pfrts, io->pfrio_size, &io->pfrio_nadd, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); free(pfrts, M_TEMP); break; } case DIOCRDELTABLES: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_table *pfrts; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_table)) { error = ENODEV; break; } totlen = io->pfrio_size * sizeof(struct pfr_table); pfrts = malloc(totlen, M_TEMP, M_WAITOK); error = copyin(io->pfrio_buffer, pfrts, totlen); if (error) { free(pfrts, M_TEMP); break; } PF_RULES_WLOCK(); error = pfr_del_tables(pfrts, io->pfrio_size, &io->pfrio_ndel, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); free(pfrts, M_TEMP); break; } case DIOCRGETTABLES: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_table *pfrts; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_table)) { error = ENODEV; break; } totlen = io->pfrio_size * sizeof(struct pfr_table); pfrts = malloc(totlen, M_TEMP, M_WAITOK); PF_RULES_RLOCK(); error = pfr_get_tables(&io->pfrio_table, pfrts, &io->pfrio_size, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_RUNLOCK(); if (error == 0) error = copyout(pfrts, io->pfrio_buffer, totlen); free(pfrts, M_TEMP); break; } case DIOCRGETTSTATS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_tstats *pfrtstats; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_tstats)) { error = ENODEV; break; } totlen = io->pfrio_size * sizeof(struct pfr_tstats); pfrtstats = malloc(totlen, M_TEMP, M_WAITOK); PF_RULES_WLOCK(); error = pfr_get_tstats(&io->pfrio_table, pfrtstats, &io->pfrio_size, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); if (error == 0) error = copyout(pfrtstats, io->pfrio_buffer, totlen); free(pfrtstats, M_TEMP); break; } case DIOCRCLRTSTATS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_table *pfrts; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_table)) { error = ENODEV; break; } totlen = io->pfrio_size * sizeof(struct pfr_table); pfrts = malloc(totlen, M_TEMP, M_WAITOK); error = copyin(io->pfrio_buffer, pfrts, totlen); if (error) { free(pfrts, M_TEMP); break; } PF_RULES_WLOCK(); error = pfr_clr_tstats(pfrts, io->pfrio_size, &io->pfrio_nzero, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); free(pfrts, M_TEMP); break; } case DIOCRSETTFLAGS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_table *pfrts; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_table)) { error = ENODEV; break; } totlen = io->pfrio_size * sizeof(struct pfr_table); pfrts = malloc(totlen, M_TEMP, M_WAITOK); error = copyin(io->pfrio_buffer, pfrts, totlen); if (error) { free(pfrts, M_TEMP); break; } PF_RULES_WLOCK(); error = pfr_set_tflags(pfrts, io->pfrio_size, io->pfrio_setflag, io->pfrio_clrflag, &io->pfrio_nchange, &io->pfrio_ndel, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); free(pfrts, M_TEMP); break; } case DIOCRCLRADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != 0) { error = ENODEV; break; } PF_RULES_WLOCK(); error = pfr_clr_addrs(&io->pfrio_table, &io->pfrio_ndel, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); break; } case DIOCRADDADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_addr *pfras; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; break; } totlen = io->pfrio_size * sizeof(struct pfr_addr); pfras = malloc(totlen, M_TEMP, M_WAITOK); error = copyin(io->pfrio_buffer, pfras, totlen); if (error) { free(pfras, M_TEMP); break; } PF_RULES_WLOCK(); error = pfr_add_addrs(&io->pfrio_table, pfras, io->pfrio_size, &io->pfrio_nadd, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); if (error == 0 && io->pfrio_flags & PFR_FLAG_FEEDBACK) error = copyout(pfras, io->pfrio_buffer, totlen); free(pfras, M_TEMP); break; } case DIOCRDELADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_addr *pfras; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; break; } totlen = io->pfrio_size * sizeof(struct pfr_addr); pfras = malloc(totlen, M_TEMP, M_WAITOK); error = copyin(io->pfrio_buffer, pfras, totlen); if (error) { free(pfras, M_TEMP); break; } PF_RULES_WLOCK(); error = pfr_del_addrs(&io->pfrio_table, pfras, io->pfrio_size, &io->pfrio_ndel, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); if (error == 0 && io->pfrio_flags & PFR_FLAG_FEEDBACK) error = copyout(pfras, io->pfrio_buffer, totlen); free(pfras, M_TEMP); break; } case DIOCRSETADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_addr *pfras; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; break; } totlen = (io->pfrio_size + io->pfrio_size2) * sizeof(struct pfr_addr); pfras = malloc(totlen, M_TEMP, M_WAITOK); error = copyin(io->pfrio_buffer, pfras, totlen); if (error) { free(pfras, M_TEMP); break; } PF_RULES_WLOCK(); error = pfr_set_addrs(&io->pfrio_table, pfras, io->pfrio_size, &io->pfrio_size2, &io->pfrio_nadd, &io->pfrio_ndel, &io->pfrio_nchange, io->pfrio_flags | PFR_FLAG_USERIOCTL, 0); PF_RULES_WUNLOCK(); if (error == 0 && io->pfrio_flags & PFR_FLAG_FEEDBACK) error = copyout(pfras, io->pfrio_buffer, totlen); free(pfras, M_TEMP); break; } case DIOCRGETADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_addr *pfras; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; break; } totlen = io->pfrio_size * sizeof(struct pfr_addr); pfras = malloc(totlen, M_TEMP, M_WAITOK); PF_RULES_RLOCK(); error = pfr_get_addrs(&io->pfrio_table, pfras, &io->pfrio_size, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_RUNLOCK(); if (error == 0) error = copyout(pfras, io->pfrio_buffer, totlen); free(pfras, M_TEMP); break; } case DIOCRGETASTATS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_astats *pfrastats; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_astats)) { error = ENODEV; break; } totlen = io->pfrio_size * sizeof(struct pfr_astats); pfrastats = malloc(totlen, M_TEMP, M_WAITOK); PF_RULES_RLOCK(); error = pfr_get_astats(&io->pfrio_table, pfrastats, &io->pfrio_size, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_RUNLOCK(); if (error == 0) error = copyout(pfrastats, io->pfrio_buffer, totlen); free(pfrastats, M_TEMP); break; } case DIOCRCLRASTATS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_addr *pfras; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; break; } totlen = io->pfrio_size * sizeof(struct pfr_addr); pfras = malloc(totlen, M_TEMP, M_WAITOK); error = copyin(io->pfrio_buffer, pfras, totlen); if (error) { free(pfras, M_TEMP); break; } PF_RULES_WLOCK(); error = pfr_clr_astats(&io->pfrio_table, pfras, io->pfrio_size, &io->pfrio_nzero, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); if (error == 0 && io->pfrio_flags & PFR_FLAG_FEEDBACK) error = copyout(pfras, io->pfrio_buffer, totlen); free(pfras, M_TEMP); break; } case DIOCRTSTADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_addr *pfras; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; break; } totlen = io->pfrio_size * sizeof(struct pfr_addr); pfras = malloc(totlen, M_TEMP, M_WAITOK); error = copyin(io->pfrio_buffer, pfras, totlen); if (error) { free(pfras, M_TEMP); break; } PF_RULES_RLOCK(); error = pfr_tst_addrs(&io->pfrio_table, pfras, io->pfrio_size, &io->pfrio_nmatch, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_RUNLOCK(); if (error == 0) error = copyout(pfras, io->pfrio_buffer, totlen); free(pfras, M_TEMP); break; } case DIOCRINADEFINE: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_addr *pfras; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; break; } totlen = io->pfrio_size * sizeof(struct pfr_addr); pfras = malloc(totlen, M_TEMP, M_WAITOK); error = copyin(io->pfrio_buffer, pfras, totlen); if (error) { free(pfras, M_TEMP); break; } PF_RULES_WLOCK(); error = pfr_ina_define(&io->pfrio_table, pfras, io->pfrio_size, &io->pfrio_nadd, &io->pfrio_naddr, io->pfrio_ticket, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); free(pfras, M_TEMP); break; } case DIOCOSFPADD: { struct pf_osfp_ioctl *io = (struct pf_osfp_ioctl *)addr; PF_RULES_WLOCK(); error = pf_osfp_add(io); PF_RULES_WUNLOCK(); break; } case DIOCOSFPGET: { struct pf_osfp_ioctl *io = (struct pf_osfp_ioctl *)addr; PF_RULES_RLOCK(); error = pf_osfp_get(io); PF_RULES_RUNLOCK(); break; } case DIOCXBEGIN: { struct pfioc_trans *io = (struct pfioc_trans *)addr; struct pfioc_trans_e *ioes, *ioe; size_t totlen; int i; if (io->esize != sizeof(*ioe)) { error = ENODEV; break; } totlen = sizeof(struct pfioc_trans_e) * io->size; ioes = malloc(totlen, M_TEMP, M_WAITOK); error = copyin(io->array, ioes, totlen); if (error) { free(ioes, M_TEMP); break; } PF_RULES_WLOCK(); for (i = 0, ioe = ioes; i < io->size; i++, ioe++) { switch (ioe->rs_num) { #ifdef ALTQ case PF_RULESET_ALTQ: if (ioe->anchor[0]) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); error = EINVAL; goto fail; } if ((error = pf_begin_altq(&ioe->ticket))) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); goto fail; } break; #endif /* ALTQ */ case PF_RULESET_TABLE: { struct pfr_table table; bzero(&table, sizeof(table)); strlcpy(table.pfrt_anchor, ioe->anchor, sizeof(table.pfrt_anchor)); if ((error = pfr_ina_begin(&table, &ioe->ticket, NULL, 0))) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); goto fail; } break; } default: if ((error = pf_begin_rules(&ioe->ticket, ioe->rs_num, ioe->anchor))) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); goto fail; } break; } } PF_RULES_WUNLOCK(); error = copyout(ioes, io->array, totlen); free(ioes, M_TEMP); break; } case DIOCXROLLBACK: { struct pfioc_trans *io = (struct pfioc_trans *)addr; struct pfioc_trans_e *ioe, *ioes; size_t totlen; int i; if (io->esize != sizeof(*ioe)) { error = ENODEV; break; } totlen = sizeof(struct pfioc_trans_e) * io->size; ioes = malloc(totlen, M_TEMP, M_WAITOK); error = copyin(io->array, ioes, totlen); if (error) { free(ioes, M_TEMP); break; } PF_RULES_WLOCK(); for (i = 0, ioe = ioes; i < io->size; i++, ioe++) { switch (ioe->rs_num) { #ifdef ALTQ case PF_RULESET_ALTQ: if (ioe->anchor[0]) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); error = EINVAL; goto fail; } if ((error = pf_rollback_altq(ioe->ticket))) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); goto fail; /* really bad */ } break; #endif /* ALTQ */ case PF_RULESET_TABLE: { struct pfr_table table; bzero(&table, sizeof(table)); strlcpy(table.pfrt_anchor, ioe->anchor, sizeof(table.pfrt_anchor)); if ((error = pfr_ina_rollback(&table, ioe->ticket, NULL, 0))) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); goto fail; /* really bad */ } break; } default: if ((error = pf_rollback_rules(ioe->ticket, ioe->rs_num, ioe->anchor))) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); goto fail; /* really bad */ } break; } } PF_RULES_WUNLOCK(); free(ioes, M_TEMP); break; } case DIOCXCOMMIT: { struct pfioc_trans *io = (struct pfioc_trans *)addr; struct pfioc_trans_e *ioe, *ioes; struct pf_ruleset *rs; size_t totlen; int i; if (io->esize != sizeof(*ioe)) { error = ENODEV; break; } totlen = sizeof(struct pfioc_trans_e) * io->size; ioes = malloc(totlen, M_TEMP, M_WAITOK); error = copyin(io->array, ioes, totlen); if (error) { free(ioes, M_TEMP); break; } PF_RULES_WLOCK(); /* First makes sure everything will succeed. */ for (i = 0, ioe = ioes; i < io->size; i++, ioe++) { switch (ioe->rs_num) { #ifdef ALTQ case PF_RULESET_ALTQ: if (ioe->anchor[0]) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); error = EINVAL; goto fail; } if (!V_altqs_inactive_open || ioe->ticket != V_ticket_altqs_inactive) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); error = EBUSY; goto fail; } break; #endif /* ALTQ */ case PF_RULESET_TABLE: rs = pf_find_ruleset(ioe->anchor); if (rs == NULL || !rs->topen || ioe->ticket != rs->tticket) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); error = EBUSY; goto fail; } break; default: if (ioe->rs_num < 0 || ioe->rs_num >= PF_RULESET_MAX) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); error = EINVAL; goto fail; } rs = pf_find_ruleset(ioe->anchor); if (rs == NULL || !rs->rules[ioe->rs_num].inactive.open || rs->rules[ioe->rs_num].inactive.ticket != ioe->ticket) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); error = EBUSY; goto fail; } break; } } /* Now do the commit - no errors should happen here. */ for (i = 0, ioe = ioes; i < io->size; i++, ioe++) { switch (ioe->rs_num) { #ifdef ALTQ case PF_RULESET_ALTQ: if ((error = pf_commit_altq(ioe->ticket))) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); goto fail; /* really bad */ } break; #endif /* ALTQ */ case PF_RULESET_TABLE: { struct pfr_table table; bzero(&table, sizeof(table)); strlcpy(table.pfrt_anchor, ioe->anchor, sizeof(table.pfrt_anchor)); if ((error = pfr_ina_commit(&table, ioe->ticket, NULL, NULL, 0))) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); goto fail; /* really bad */ } break; } default: if ((error = pf_commit_rules(ioe->ticket, ioe->rs_num, ioe->anchor))) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); goto fail; /* really bad */ } break; } } PF_RULES_WUNLOCK(); free(ioes, M_TEMP); break; } case DIOCGETSRCNODES: { struct pfioc_src_nodes *psn = (struct pfioc_src_nodes *)addr; struct pf_srchash *sh; struct pf_src_node *n, *p, *pstore; uint32_t i, nr = 0; if (psn->psn_len == 0) { for (i = 0, sh = V_pf_srchash; i <= pf_srchashmask; i++, sh++) { PF_HASHROW_LOCK(sh); LIST_FOREACH(n, &sh->nodes, entry) nr++; PF_HASHROW_UNLOCK(sh); } psn->psn_len = sizeof(struct pf_src_node) * nr; break; } p = pstore = malloc(psn->psn_len, M_TEMP, M_WAITOK); for (i = 0, sh = V_pf_srchash; i <= pf_srchashmask; i++, sh++) { PF_HASHROW_LOCK(sh); LIST_FOREACH(n, &sh->nodes, entry) { int secs = time_uptime, diff; if ((nr + 1) * sizeof(*p) > (unsigned)psn->psn_len) break; bcopy(n, p, sizeof(struct pf_src_node)); if (n->rule.ptr != NULL) p->rule.nr = n->rule.ptr->nr; p->creation = secs - p->creation; if (p->expire > secs) p->expire -= secs; else p->expire = 0; /* Adjust the connection rate estimate. */ diff = secs - n->conn_rate.last; if (diff >= n->conn_rate.seconds) p->conn_rate.count = 0; else p->conn_rate.count -= n->conn_rate.count * diff / n->conn_rate.seconds; p++; nr++; } PF_HASHROW_UNLOCK(sh); } error = copyout(pstore, psn->psn_src_nodes, sizeof(struct pf_src_node) * nr); if (error) { free(pstore, M_TEMP); break; } psn->psn_len = sizeof(struct pf_src_node) * nr; free(pstore, M_TEMP); break; } case DIOCCLRSRCNODES: { pf_clear_srcnodes(NULL); pf_purge_expired_src_nodes(); break; } case DIOCKILLSRCNODES: pf_kill_srcnodes((struct pfioc_src_node_kill *)addr); break; case DIOCSETHOSTID: { u_int32_t *hostid = (u_int32_t *)addr; PF_RULES_WLOCK(); if (*hostid == 0) V_pf_status.hostid = arc4random(); else V_pf_status.hostid = *hostid; PF_RULES_WUNLOCK(); break; } case DIOCOSFPFLUSH: PF_RULES_WLOCK(); pf_osfp_flush(); PF_RULES_WUNLOCK(); break; case DIOCIGETIFACES: { struct pfioc_iface *io = (struct pfioc_iface *)addr; struct pfi_kif *ifstore; size_t bufsiz; if (io->pfiio_esize != sizeof(struct pfi_kif)) { error = ENODEV; break; } bufsiz = io->pfiio_size * sizeof(struct pfi_kif); ifstore = malloc(bufsiz, M_TEMP, M_WAITOK); PF_RULES_RLOCK(); pfi_get_ifaces(io->pfiio_name, ifstore, &io->pfiio_size); PF_RULES_RUNLOCK(); error = copyout(ifstore, io->pfiio_buffer, bufsiz); free(ifstore, M_TEMP); break; } case DIOCSETIFFLAG: { struct pfioc_iface *io = (struct pfioc_iface *)addr; PF_RULES_WLOCK(); error = pfi_set_flags(io->pfiio_name, io->pfiio_flags); PF_RULES_WUNLOCK(); break; } case DIOCCLRIFFLAG: { struct pfioc_iface *io = (struct pfioc_iface *)addr; PF_RULES_WLOCK(); error = pfi_clear_flags(io->pfiio_name, io->pfiio_flags); PF_RULES_WUNLOCK(); break; } default: error = ENODEV; break; } fail: if (sx_xlocked(&pf_ioctl_lock)) sx_xunlock(&pf_ioctl_lock); CURVNET_RESTORE(); return (error); } void pfsync_state_export(struct pfsync_state *sp, struct pf_state *st) { bzero(sp, sizeof(struct pfsync_state)); /* copy from state key */ sp->key[PF_SK_WIRE].addr[0] = st->key[PF_SK_WIRE]->addr[0]; sp->key[PF_SK_WIRE].addr[1] = st->key[PF_SK_WIRE]->addr[1]; sp->key[PF_SK_WIRE].port[0] = st->key[PF_SK_WIRE]->port[0]; sp->key[PF_SK_WIRE].port[1] = st->key[PF_SK_WIRE]->port[1]; sp->key[PF_SK_STACK].addr[0] = st->key[PF_SK_STACK]->addr[0]; sp->key[PF_SK_STACK].addr[1] = st->key[PF_SK_STACK]->addr[1]; sp->key[PF_SK_STACK].port[0] = st->key[PF_SK_STACK]->port[0]; sp->key[PF_SK_STACK].port[1] = st->key[PF_SK_STACK]->port[1]; sp->proto = st->key[PF_SK_WIRE]->proto; sp->af = st->key[PF_SK_WIRE]->af; /* copy from state */ strlcpy(sp->ifname, st->kif->pfik_name, sizeof(sp->ifname)); bcopy(&st->rt_addr, &sp->rt_addr, sizeof(sp->rt_addr)); sp->creation = htonl(time_uptime - st->creation); sp->expire = pf_state_expires(st); if (sp->expire <= time_uptime) sp->expire = htonl(0); else sp->expire = htonl(sp->expire - time_uptime); sp->direction = st->direction; sp->log = st->log; sp->timeout = st->timeout; sp->state_flags = st->state_flags; if (st->src_node) sp->sync_flags |= PFSYNC_FLAG_SRCNODE; if (st->nat_src_node) sp->sync_flags |= PFSYNC_FLAG_NATSRCNODE; sp->id = st->id; sp->creatorid = st->creatorid; pf_state_peer_hton(&st->src, &sp->src); pf_state_peer_hton(&st->dst, &sp->dst); if (st->rule.ptr == NULL) sp->rule = htonl(-1); else sp->rule = htonl(st->rule.ptr->nr); if (st->anchor.ptr == NULL) sp->anchor = htonl(-1); else sp->anchor = htonl(st->anchor.ptr->nr); if (st->nat_rule.ptr == NULL) sp->nat_rule = htonl(-1); else sp->nat_rule = htonl(st->nat_rule.ptr->nr); pf_state_counter_hton(st->packets[0], sp->packets[0]); pf_state_counter_hton(st->packets[1], sp->packets[1]); pf_state_counter_hton(st->bytes[0], sp->bytes[0]); pf_state_counter_hton(st->bytes[1], sp->bytes[1]); } static void pf_tbladdr_copyout(struct pf_addr_wrap *aw) { struct pfr_ktable *kt; KASSERT(aw->type == PF_ADDR_TABLE, ("%s: type %u", __func__, aw->type)); kt = aw->p.tbl; if (!(kt->pfrkt_flags & PFR_TFLAG_ACTIVE) && kt->pfrkt_root != NULL) kt = kt->pfrkt_root; aw->p.tbl = NULL; aw->p.tblcnt = (kt->pfrkt_flags & PFR_TFLAG_ACTIVE) ? kt->pfrkt_cnt : -1; } /* * XXX - Check for version missmatch!!! */ static void pf_clear_states(void) { struct pf_state *s; u_int i; for (i = 0; i <= pf_hashmask; i++) { struct pf_idhash *ih = &V_pf_idhash[i]; relock: PF_HASHROW_LOCK(ih); LIST_FOREACH(s, &ih->states, entry) { s->timeout = PFTM_PURGE; /* Don't send out individual delete messages. */ s->sync_state = PFSTATE_NOSYNC; pf_unlink_state(s, PF_ENTER_LOCKED); goto relock; } PF_HASHROW_UNLOCK(ih); } } static int pf_clear_tables(void) { struct pfioc_table io; int error; bzero(&io, sizeof(io)); error = pfr_clr_tables(&io.pfrio_table, &io.pfrio_ndel, io.pfrio_flags); return (error); } static void pf_clear_srcnodes(struct pf_src_node *n) { struct pf_state *s; int i; for (i = 0; i <= pf_hashmask; i++) { struct pf_idhash *ih = &V_pf_idhash[i]; PF_HASHROW_LOCK(ih); LIST_FOREACH(s, &ih->states, entry) { if (n == NULL || n == s->src_node) s->src_node = NULL; if (n == NULL || n == s->nat_src_node) s->nat_src_node = NULL; } PF_HASHROW_UNLOCK(ih); } if (n == NULL) { struct pf_srchash *sh; for (i = 0, sh = V_pf_srchash; i <= pf_srchashmask; i++, sh++) { PF_HASHROW_LOCK(sh); LIST_FOREACH(n, &sh->nodes, entry) { n->expire = 1; n->states = 0; } PF_HASHROW_UNLOCK(sh); } } else { /* XXX: hash slot should already be locked here. */ n->expire = 1; n->states = 0; } } static void pf_kill_srcnodes(struct pfioc_src_node_kill *psnk) { struct pf_src_node_list kill; LIST_INIT(&kill); for (int i = 0; i <= pf_srchashmask; i++) { struct pf_srchash *sh = &V_pf_srchash[i]; struct pf_src_node *sn, *tmp; PF_HASHROW_LOCK(sh); LIST_FOREACH_SAFE(sn, &sh->nodes, entry, tmp) if (PF_MATCHA(psnk->psnk_src.neg, &psnk->psnk_src.addr.v.a.addr, &psnk->psnk_src.addr.v.a.mask, &sn->addr, sn->af) && PF_MATCHA(psnk->psnk_dst.neg, &psnk->psnk_dst.addr.v.a.addr, &psnk->psnk_dst.addr.v.a.mask, &sn->raddr, sn->af)) { pf_unlink_src_node_locked(sn); LIST_INSERT_HEAD(&kill, sn, entry); sn->expire = 1; } PF_HASHROW_UNLOCK(sh); } for (int i = 0; i <= pf_hashmask; i++) { struct pf_idhash *ih = &V_pf_idhash[i]; struct pf_state *s; PF_HASHROW_LOCK(ih); LIST_FOREACH(s, &ih->states, entry) { if (s->src_node && s->src_node->expire == 1) { #ifdef INVARIANTS s->src_node->states--; #endif s->src_node = NULL; } if (s->nat_src_node && s->nat_src_node->expire == 1) { #ifdef INVARIANTS s->nat_src_node->states--; #endif s->nat_src_node = NULL; } } PF_HASHROW_UNLOCK(ih); } psnk->psnk_killed = pf_free_src_nodes(&kill); } /* * XXX - Check for version missmatch!!! */ /* * Duplicate pfctl -Fa operation to get rid of as much as we can. */ static int shutdown_pf(void) { int error = 0; u_int32_t t[5]; char nn = '\0'; V_pf_status.running = 0; counter_u64_free(V_pf_default_rule.states_cur); counter_u64_free(V_pf_default_rule.states_tot); counter_u64_free(V_pf_default_rule.src_nodes); for (int i = 0; i < PFRES_MAX; i++) counter_u64_free(V_pf_status.counters[i]); for (int i = 0; i < LCNT_MAX; i++) counter_u64_free(V_pf_status.lcounters[i]); for (int i = 0; i < FCNT_MAX; i++) counter_u64_free(V_pf_status.fcounters[i]); for (int i = 0; i < SCNT_MAX; i++) counter_u64_free(V_pf_status.scounters[i]); do { if ((error = pf_begin_rules(&t[0], PF_RULESET_SCRUB, &nn)) != 0) { DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: SCRUB\n")); break; } if ((error = pf_begin_rules(&t[1], PF_RULESET_FILTER, &nn)) != 0) { DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: FILTER\n")); break; /* XXX: rollback? */ } if ((error = pf_begin_rules(&t[2], PF_RULESET_NAT, &nn)) != 0) { DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: NAT\n")); break; /* XXX: rollback? */ } if ((error = pf_begin_rules(&t[3], PF_RULESET_BINAT, &nn)) != 0) { DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: BINAT\n")); break; /* XXX: rollback? */ } if ((error = pf_begin_rules(&t[4], PF_RULESET_RDR, &nn)) != 0) { DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: RDR\n")); break; /* XXX: rollback? */ } /* XXX: these should always succeed here */ pf_commit_rules(t[0], PF_RULESET_SCRUB, &nn); pf_commit_rules(t[1], PF_RULESET_FILTER, &nn); pf_commit_rules(t[2], PF_RULESET_NAT, &nn); pf_commit_rules(t[3], PF_RULESET_BINAT, &nn); pf_commit_rules(t[4], PF_RULESET_RDR, &nn); if ((error = pf_clear_tables()) != 0) break; #ifdef ALTQ if ((error = pf_begin_altq(&t[0])) != 0) { DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: ALTQ\n")); break; } pf_commit_altq(t[0]); #endif pf_clear_states(); pf_clear_srcnodes(NULL); /* status does not use malloced mem so no need to cleanup */ /* fingerprints and interfaces have thier own cleanup code */ } while(0); return (error); } #ifdef INET static int pf_check_in(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, struct inpcb *inp) { int chk; chk = pf_test(PF_IN, ifp, m, inp); if (chk && *m) { m_freem(*m); *m = NULL; } return (chk); } static int pf_check_out(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, struct inpcb *inp) { int chk; /* We need a proper CSUM befor we start (s. OpenBSD ip_output) */ if ((*m)->m_pkthdr.csum_flags & CSUM_DELAY_DATA) { in_delayed_cksum(*m); (*m)->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA; } chk = pf_test(PF_OUT, ifp, m, inp); if (chk && *m) { m_freem(*m); *m = NULL; } return (chk); } #endif #ifdef INET6 static int pf_check6_in(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, struct inpcb *inp) { int chk; /* * In case of loopback traffic IPv6 uses the real interface in * order to support scoped addresses. In order to support stateful * filtering we have change this to lo0 as it is the case in IPv4. */ CURVNET_SET(ifp->if_vnet); chk = pf_test6(PF_IN, (*m)->m_flags & M_LOOP ? V_loif : ifp, m, inp); CURVNET_RESTORE(); if (chk && *m) { m_freem(*m); *m = NULL; } return chk; } static int pf_check6_out(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, struct inpcb *inp) { int chk; /* We need a proper CSUM before we start (s. OpenBSD ip_output) */ - if ((*m)->m_pkthdr.csum_flags & CSUM_DELAY_DATA) { -#ifdef INET - /* XXX-BZ copy&paste error from r126261? */ - in_delayed_cksum(*m); -#endif - (*m)->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA; + if ((*m)->m_pkthdr.csum_flags & CSUM_DELAY_DATA_IPV6) { + in6_delayed_cksum(*m, + (*m)->m_pkthdr.len - sizeof(struct ip6_hdr), + sizeof(struct ip6_hdr)); + (*m)->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA_IPV6; } CURVNET_SET(ifp->if_vnet); chk = pf_test6(PF_OUT, ifp, m, inp); CURVNET_RESTORE(); if (chk && *m) { m_freem(*m); *m = NULL; } return chk; } #endif /* INET6 */ static int hook_pf(void) { #ifdef INET struct pfil_head *pfh_inet; #endif #ifdef INET6 struct pfil_head *pfh_inet6; #endif if (V_pf_pfil_hooked) return (0); #ifdef INET pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); if (pfh_inet == NULL) return (ESRCH); /* XXX */ pfil_add_hook(pf_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet); pfil_add_hook(pf_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet); #endif #ifdef INET6 pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6); if (pfh_inet6 == NULL) { #ifdef INET pfil_remove_hook(pf_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet); pfil_remove_hook(pf_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet); #endif return (ESRCH); /* XXX */ } pfil_add_hook(pf_check6_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet6); pfil_add_hook(pf_check6_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet6); #endif V_pf_pfil_hooked = 1; return (0); } static int dehook_pf(void) { #ifdef INET struct pfil_head *pfh_inet; #endif #ifdef INET6 struct pfil_head *pfh_inet6; #endif if (V_pf_pfil_hooked == 0) return (0); #ifdef INET pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); if (pfh_inet == NULL) return (ESRCH); /* XXX */ pfil_remove_hook(pf_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet); pfil_remove_hook(pf_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet); #endif #ifdef INET6 pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6); if (pfh_inet6 == NULL) return (ESRCH); /* XXX */ pfil_remove_hook(pf_check6_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet6); pfil_remove_hook(pf_check6_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet6); #endif V_pf_pfil_hooked = 0; return (0); } static int pf_load(void) { int error; VNET_ITERATOR_DECL(vnet_iter); VNET_LIST_RLOCK(); VNET_FOREACH(vnet_iter) { CURVNET_SET(vnet_iter); V_pf_pfil_hooked = 0; V_pf_end_threads = 0; TAILQ_INIT(&V_pf_tags); TAILQ_INIT(&V_pf_qids); CURVNET_RESTORE(); } VNET_LIST_RUNLOCK(); rw_init(&pf_rules_lock, "pf rulesets"); sx_init(&pf_ioctl_lock, "pf ioctl"); pf_dev = make_dev(&pf_cdevsw, 0, 0, 0, 0600, PF_NAME); if ((error = pfattach()) != 0) return (error); return (0); } static int pf_unload(void) { int error = 0; V_pf_status.running = 0; swi_remove(V_pf_swi_cookie); error = dehook_pf(); if (error) { /* * Should not happen! * XXX Due to error code ESRCH, kldunload will show * a message like 'No such process'. */ printf("%s : pfil unregisteration fail\n", __FUNCTION__); return error; } PF_RULES_WLOCK(); shutdown_pf(); V_pf_end_threads = 1; while (V_pf_end_threads < 2) { wakeup_one(pf_purge_thread); rw_sleep(pf_purge_thread, &pf_rules_lock, 0, "pftmo", 0); } pf_normalize_cleanup(); pfi_cleanup(); pfr_cleanup(); pf_osfp_flush(); pf_cleanup(); if (IS_DEFAULT_VNET(curvnet)) pf_mtag_cleanup(); PF_RULES_WUNLOCK(); destroy_dev(pf_dev); rw_destroy(&pf_rules_lock); sx_destroy(&pf_ioctl_lock); return (error); } static int pf_modevent(module_t mod, int type, void *data) { int error = 0; switch(type) { case MOD_LOAD: error = pf_load(); break; case MOD_QUIESCE: /* * Module should not be unloaded due to race conditions. */ error = EBUSY; break; case MOD_UNLOAD: error = pf_unload(); break; default: error = EINVAL; break; } return (error); } static moduledata_t pf_mod = { "pf", pf_modevent, 0 }; DECLARE_MODULE(pf, pf_mod, SI_SUB_PSEUDO, SI_ORDER_FIRST); MODULE_VERSION(pf, PF_MODVER); Index: projects/sendfile/sys/vm/vnode_pager.c =================================================================== --- projects/sendfile/sys/vm/vnode_pager.c (revision 274716) +++ projects/sendfile/sys/vm/vnode_pager.c (revision 274717) @@ -1,1386 +1,1386 @@ /*- * Copyright (c) 1990 University of Utah. * Copyright (c) 1991 The Regents of the University of California. * All rights reserved. * Copyright (c) 1993, 1994 John S. Dyson * Copyright (c) 1995, David Greenman * * This code is derived from software contributed to Berkeley by * the Systems Programming Group of the University of Utah Computer * Science Department. * * 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. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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. * * from: @(#)vnode_pager.c 7.5 (Berkeley) 4/20/91 */ /* * Page to/from files (vnodes). */ /* * TODO: * Implement VOP_GETPAGES/PUTPAGES interface for filesystems. Will * greatly re-simplify the vnode_pager. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Structure to pass state from vnode_pager_generic_getpages() * to vnode_pager_generic_getpages_done_async(). */ struct getpages_data { void (*iodone)(void *, vm_page_t *, int, int); void *arg; }; static int vnode_pager_addr(struct vnode *vp, vm_ooffset_t address, daddr_t *rtaddress, int *run); static int vnode_pager_input_smlfs(vm_object_t object, vm_page_t m); static int vnode_pager_input_old(vm_object_t object, vm_page_t m); static void vnode_pager_dealloc(vm_object_t); static int vnode_pager_local_getpages0(struct vnode *, vm_page_t *, int, int, void (*)(void *, vm_page_t *, int, int), void *); static int vnode_pager_getpages(vm_object_t, vm_page_t *, int, int); static int vnode_pager_getpages_async(vm_object_t, vm_page_t *, int, int, void(*)(void *, vm_page_t *, int, int), void *); static void vnode_pager_putpages(vm_object_t, vm_page_t *, int, int, int *); static boolean_t vnode_pager_haspage(vm_object_t, vm_pindex_t, int *, int *); static vm_object_t vnode_pager_alloc(void *, vm_ooffset_t, vm_prot_t, vm_ooffset_t, struct ucred *cred); static int vnode_pager_generic_getpages_done(struct buf *); static void vnode_pager_generic_getpages_done_async(struct buf *); struct pagerops vnodepagerops = { .pgo_alloc = vnode_pager_alloc, .pgo_dealloc = vnode_pager_dealloc, .pgo_getpages = vnode_pager_getpages, .pgo_getpages_async = vnode_pager_getpages_async, .pgo_putpages = vnode_pager_putpages, .pgo_haspage = vnode_pager_haspage, }; int vnode_pbuf_freecnt; /* Create the VM system backing object for this vnode */ int vnode_create_vobject(struct vnode *vp, off_t isize, struct thread *td) { vm_object_t object; vm_ooffset_t size = isize; struct vattr va; if (!vn_isdisk(vp, NULL) && vn_canvmio(vp) == FALSE) return (0); while ((object = vp->v_object) != NULL) { VM_OBJECT_WLOCK(object); if (!(object->flags & OBJ_DEAD)) { VM_OBJECT_WUNLOCK(object); return (0); } VOP_UNLOCK(vp, 0); vm_object_set_flag(object, OBJ_DISCONNECTWNT); VM_OBJECT_SLEEP(object, object, PDROP | PVM, "vodead", 0); vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); } if (size == 0) { if (vn_isdisk(vp, NULL)) { size = IDX_TO_OFF(INT_MAX); } else { if (VOP_GETATTR(vp, &va, td->td_ucred)) return (0); size = va.va_size; } } object = vnode_pager_alloc(vp, size, 0, 0, td->td_ucred); /* * Dereference the reference we just created. This assumes * that the object is associated with the vp. */ VM_OBJECT_WLOCK(object); object->ref_count--; VM_OBJECT_WUNLOCK(object); vrele(vp); KASSERT(vp->v_object != NULL, ("vnode_create_vobject: NULL object")); return (0); } void vnode_destroy_vobject(struct vnode *vp) { struct vm_object *obj; obj = vp->v_object; if (obj == NULL) return; ASSERT_VOP_ELOCKED(vp, "vnode_destroy_vobject"); VM_OBJECT_WLOCK(obj); if (obj->ref_count == 0) { /* * don't double-terminate the object */ if ((obj->flags & OBJ_DEAD) == 0) vm_object_terminate(obj); else VM_OBJECT_WUNLOCK(obj); } else { /* * Woe to the process that tries to page now :-). */ vm_pager_deallocate(obj); VM_OBJECT_WUNLOCK(obj); } vp->v_object = NULL; } /* * Allocate (or lookup) pager for a vnode. * Handle is a vnode pointer. * * MPSAFE */ vm_object_t vnode_pager_alloc(void *handle, vm_ooffset_t size, vm_prot_t prot, vm_ooffset_t offset, struct ucred *cred) { vm_object_t object; struct vnode *vp; /* * Pageout to vnode, no can do yet. */ if (handle == NULL) return (NULL); vp = (struct vnode *) handle; /* * If the object is being terminated, wait for it to * go away. */ retry: while ((object = vp->v_object) != NULL) { VM_OBJECT_WLOCK(object); if ((object->flags & OBJ_DEAD) == 0) break; vm_object_set_flag(object, OBJ_DISCONNECTWNT); VM_OBJECT_SLEEP(object, object, PDROP | PVM, "vadead", 0); } KASSERT(vp->v_usecount != 0, ("vnode_pager_alloc: no vnode reference")); if (object == NULL) { /* * Add an object of the appropriate size */ object = vm_object_allocate(OBJT_VNODE, OFF_TO_IDX(round_page(size))); object->un_pager.vnp.vnp_size = size; object->un_pager.vnp.writemappings = 0; object->handle = handle; VI_LOCK(vp); if (vp->v_object != NULL) { /* * Object has been created while we were sleeping */ VI_UNLOCK(vp); vm_object_destroy(object); goto retry; } vp->v_object = object; VI_UNLOCK(vp); } else { object->ref_count++; VM_OBJECT_WUNLOCK(object); } vref(vp); return (object); } /* * The object must be locked. */ static void vnode_pager_dealloc(vm_object_t object) { struct vnode *vp; int refs; vp = object->handle; if (vp == NULL) panic("vnode_pager_dealloc: pager already dealloced"); VM_OBJECT_ASSERT_WLOCKED(object); vm_object_pip_wait(object, "vnpdea"); refs = object->ref_count; object->handle = NULL; object->type = OBJT_DEAD; if (object->flags & OBJ_DISCONNECTWNT) { vm_object_clear_flag(object, OBJ_DISCONNECTWNT); wakeup(object); } ASSERT_VOP_ELOCKED(vp, "vnode_pager_dealloc"); if (object->un_pager.vnp.writemappings > 0) { object->un_pager.vnp.writemappings = 0; VOP_ADD_WRITECOUNT(vp, -1); CTR3(KTR_VFS, "%s: vp %p v_writecount decreased to %d", __func__, vp, vp->v_writecount); } vp->v_object = NULL; VOP_UNSET_TEXT(vp); VM_OBJECT_WUNLOCK(object); while (refs-- > 0) vunref(vp); VM_OBJECT_WLOCK(object); } static boolean_t vnode_pager_haspage(vm_object_t object, vm_pindex_t pindex, int *before, int *after) { struct vnode *vp = object->handle; daddr_t bn; int err; daddr_t reqblock; int poff; int bsize; int pagesperblock, blocksperpage; VM_OBJECT_ASSERT_WLOCKED(object); /* * If no vp or vp is doomed or marked transparent to VM, we do not * have the page. */ if (vp == NULL || vp->v_iflag & VI_DOOMED) return FALSE; /* * If the offset is beyond end of file we do * not have the page. */ if (IDX_TO_OFF(pindex) >= object->un_pager.vnp.vnp_size) return FALSE; bsize = vp->v_mount->mnt_stat.f_iosize; pagesperblock = bsize / PAGE_SIZE; blocksperpage = 0; if (pagesperblock > 0) { reqblock = pindex / pagesperblock; } else { blocksperpage = (PAGE_SIZE / bsize); reqblock = pindex * blocksperpage; } VM_OBJECT_WUNLOCK(object); err = VOP_BMAP(vp, reqblock, NULL, &bn, after, before); VM_OBJECT_WLOCK(object); if (err) return TRUE; if (bn == -1) return FALSE; if (pagesperblock > 0) { poff = pindex - (reqblock * pagesperblock); if (before) { *before *= pagesperblock; *before += poff; } if (after) { int numafter; *after *= pagesperblock; numafter = pagesperblock - (poff + 1); if (IDX_TO_OFF(pindex + numafter) > object->un_pager.vnp.vnp_size) { numafter = OFF_TO_IDX(object->un_pager.vnp.vnp_size) - pindex; } *after += numafter; } } else { if (before) { *before /= blocksperpage; } if (after) { *after /= blocksperpage; } } return TRUE; } /* * Lets the VM system know about a change in size for a file. * We adjust our own internal size and flush any cached pages in * the associated object that are affected by the size change. * * Note: this routine may be invoked as a result of a pager put * operation (possibly at object termination time), so we must be careful. */ void vnode_pager_setsize(struct vnode *vp, vm_ooffset_t nsize) { vm_object_t object; vm_page_t m; vm_pindex_t nobjsize; if ((object = vp->v_object) == NULL) return; /* ASSERT_VOP_ELOCKED(vp, "vnode_pager_setsize and not locked vnode"); */ VM_OBJECT_WLOCK(object); if (object->type == OBJT_DEAD) { VM_OBJECT_WUNLOCK(object); return; } KASSERT(object->type == OBJT_VNODE, ("not vnode-backed object %p", object)); if (nsize == object->un_pager.vnp.vnp_size) { /* * Hasn't changed size */ VM_OBJECT_WUNLOCK(object); return; } nobjsize = OFF_TO_IDX(nsize + PAGE_MASK); if (nsize < object->un_pager.vnp.vnp_size) { /* * File has shrunk. Toss any cached pages beyond the new EOF. */ if (nobjsize < object->size) vm_object_page_remove(object, nobjsize, object->size, 0); /* * this gets rid of garbage at the end of a page that is now * only partially backed by the vnode. * * XXX for some reason (I don't know yet), if we take a * completely invalid page and mark it partially valid * it can screw up NFS reads, so we don't allow the case. */ if ((nsize & PAGE_MASK) && (m = vm_page_lookup(object, OFF_TO_IDX(nsize))) != NULL && m->valid != 0) { int base = (int)nsize & PAGE_MASK; int size = PAGE_SIZE - base; /* * Clear out partial-page garbage in case * the page has been mapped. */ pmap_zero_page_area(m, base, size); /* * Update the valid bits to reflect the blocks that * have been zeroed. Some of these valid bits may * have already been set. */ vm_page_set_valid_range(m, base, size); /* * Round "base" to the next block boundary so that the * dirty bit for a partially zeroed block is not * cleared. */ base = roundup2(base, DEV_BSIZE); /* * Clear out partial-page dirty bits. * * note that we do not clear out the valid * bits. This would prevent bogus_page * replacement from working properly. */ vm_page_clear_dirty(m, base, PAGE_SIZE - base); } else if ((nsize & PAGE_MASK) && vm_page_is_cached(object, OFF_TO_IDX(nsize))) { vm_page_cache_free(object, OFF_TO_IDX(nsize), nobjsize); } } object->un_pager.vnp.vnp_size = nsize; object->size = nobjsize; VM_OBJECT_WUNLOCK(object); } /* * calculate the linear (byte) disk address of specified virtual * file address */ static int vnode_pager_addr(struct vnode *vp, vm_ooffset_t address, daddr_t *rtaddress, int *run) { int bsize; int err; daddr_t vblock; daddr_t voffset; if (address < 0) return -1; if (vp->v_iflag & VI_DOOMED) return -1; bsize = vp->v_mount->mnt_stat.f_iosize; vblock = address / bsize; voffset = address % bsize; err = VOP_BMAP(vp, vblock, NULL, rtaddress, run, NULL); if (err == 0) { if (*rtaddress != -1) *rtaddress += voffset / DEV_BSIZE; if (run) { *run += 1; *run *= bsize/PAGE_SIZE; *run -= voffset/PAGE_SIZE; } } return (err); } /* * small block filesystem vnode pager input */ static int vnode_pager_input_smlfs(vm_object_t object, vm_page_t m) { struct vnode *vp; struct bufobj *bo; struct buf *bp; struct sf_buf *sf; daddr_t fileaddr; vm_offset_t bsize; vm_page_bits_t bits; int error, i; error = 0; vp = object->handle; if (vp->v_iflag & VI_DOOMED) return VM_PAGER_BAD; bsize = vp->v_mount->mnt_stat.f_iosize; VOP_BMAP(vp, 0, &bo, 0, NULL, NULL); sf = sf_buf_alloc(m, 0); for (i = 0; i < PAGE_SIZE / bsize; i++) { vm_ooffset_t address; bits = vm_page_bits(i * bsize, bsize); if (m->valid & bits) continue; address = IDX_TO_OFF(m->pindex) + i * bsize; if (address >= object->un_pager.vnp.vnp_size) { fileaddr = -1; } else { error = vnode_pager_addr(vp, address, &fileaddr, NULL); if (error) break; } if (fileaddr != -1) { bp = getpbuf(&vnode_pbuf_freecnt); /* build a minimal buffer header */ bp->b_iocmd = BIO_READ; bp->b_iodone = bdone; KASSERT(bp->b_rcred == NOCRED, ("leaking read ucred")); KASSERT(bp->b_wcred == NOCRED, ("leaking write ucred")); bp->b_rcred = crhold(curthread->td_ucred); bp->b_wcred = crhold(curthread->td_ucred); bp->b_data = (caddr_t)sf_buf_kva(sf) + i * bsize; bp->b_blkno = fileaddr; pbgetbo(bo, bp); bp->b_vp = vp; bp->b_bcount = bsize; bp->b_bufsize = bsize; bp->b_runningbufspace = bp->b_bufsize; atomic_add_long(&runningbufspace, bp->b_runningbufspace); /* do the input */ bp->b_iooffset = dbtob(bp->b_blkno); bstrategy(bp); bwait(bp, PVM, "vnsrd"); if ((bp->b_ioflags & BIO_ERROR) != 0) error = EIO; /* * free the buffer header back to the swap buffer pool */ bp->b_vp = NULL; pbrelbo(bp); relpbuf(bp, &vnode_pbuf_freecnt); if (error) break; } else bzero((caddr_t)sf_buf_kva(sf) + i * bsize, bsize); KASSERT((m->dirty & bits) == 0, ("vnode_pager_input_smlfs: page %p is dirty", m)); VM_OBJECT_WLOCK(object); m->valid |= bits; VM_OBJECT_WUNLOCK(object); } sf_buf_free(sf); if (error) { return VM_PAGER_ERROR; } return VM_PAGER_OK; } /* * old style vnode pager input routine */ static int vnode_pager_input_old(vm_object_t object, vm_page_t m) { struct uio auio; struct iovec aiov; int error; int size; struct sf_buf *sf; struct vnode *vp; VM_OBJECT_ASSERT_WLOCKED(object); error = 0; /* * Return failure if beyond current EOF */ if (IDX_TO_OFF(m->pindex) >= object->un_pager.vnp.vnp_size) { return VM_PAGER_BAD; } else { size = PAGE_SIZE; if (IDX_TO_OFF(m->pindex) + size > object->un_pager.vnp.vnp_size) size = object->un_pager.vnp.vnp_size - IDX_TO_OFF(m->pindex); vp = object->handle; VM_OBJECT_WUNLOCK(object); /* * Allocate a kernel virtual address and initialize so that * we can use VOP_READ/WRITE routines. */ sf = sf_buf_alloc(m, 0); aiov.iov_base = (caddr_t)sf_buf_kva(sf); aiov.iov_len = size; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; auio.uio_offset = IDX_TO_OFF(m->pindex); auio.uio_segflg = UIO_SYSSPACE; auio.uio_rw = UIO_READ; auio.uio_resid = size; auio.uio_td = curthread; error = VOP_READ(vp, &auio, 0, curthread->td_ucred); if (!error) { int count = size - auio.uio_resid; if (count == 0) error = EINVAL; else if (count != PAGE_SIZE) bzero((caddr_t)sf_buf_kva(sf) + count, PAGE_SIZE - count); } sf_buf_free(sf); VM_OBJECT_WLOCK(object); } KASSERT(m->dirty == 0, ("vnode_pager_input_old: page %p is dirty", m)); if (!error) m->valid = VM_PAGE_BITS_ALL; return error ? VM_PAGER_ERROR : VM_PAGER_OK; } /* * generic vnode pager input routine */ /* * Local media VFS's that do not implement their own VOP_GETPAGES * should have their VOP_GETPAGES call to vnode_pager_generic_getpages() * to implement the previous behaviour. * * All other FS's should use the bypass to get to the local media * backing vp's VOP_GETPAGES. */ static int vnode_pager_getpages(vm_object_t object, vm_page_t *m, int count, int reqpage) { int rtval; struct vnode *vp; int bytes = count * PAGE_SIZE; vp = object->handle; VM_OBJECT_WUNLOCK(object); rtval = VOP_GETPAGES(vp, m, bytes, reqpage); KASSERT(rtval != EOPNOTSUPP, ("vnode_pager: FS getpages not implemented\n")); VM_OBJECT_WLOCK(object); return rtval; } static int vnode_pager_getpages_async(vm_object_t object, vm_page_t *m, int count, int reqpage, void (*iodone)(void *, vm_page_t *, int, int), void *arg) { int rtval; struct vnode *vp; int bytes = count * PAGE_SIZE; vp = object->handle; VM_OBJECT_WUNLOCK(object); rtval = VOP_GETPAGES_ASYNC(vp, m, bytes, reqpage, 0, iodone, arg); KASSERT(rtval != EOPNOTSUPP, ("vnode_pager: FS getpages_async not implemented\n")); VM_OBJECT_WLOCK(object); return rtval; } /* * The implementation of VOP_GETPAGES() and VOP_GETPAGES_ASYNC() for * local filesystems, where partially valid pages can only occur at * the end of file. */ int vnode_pager_local_getpages(struct vop_getpages_args *ap) { return (vnode_pager_local_getpages0(ap->a_vp, ap->a_m, ap->a_count, ap->a_reqpage, NULL, NULL)); } int vnode_pager_local_getpages_async(struct vop_getpages_async_args *ap) { return (vnode_pager_local_getpages0(ap->a_vp, ap->a_m, ap->a_count, ap->a_reqpage, ap->a_vop_getpages_iodone, ap->a_arg)); } static int vnode_pager_local_getpages0(struct vnode *vp, vm_page_t *m, int bytecount, int reqpage, void (*iodone)(void *, vm_page_t *, int, int), void *arg) { vm_page_t mreq; mreq = m[reqpage]; /* * Since the caller has busied the requested page, that page's valid * field will not be changed by other threads. */ vm_page_assert_xbusied(mreq); /* * The requested page has valid blocks. Invalid part can only * exist at the end of file, and the page is made fully valid * by zeroing in vm_pager_getpages(). Free non-requested * pages, since no i/o is done to read its content. */ if (mreq->valid != 0) { vm_pager_free_nonreq(mreq->object, m, reqpage, round_page(bytecount) / PAGE_SIZE); if (iodone != NULL) iodone(arg, m, reqpage, 0); return (VM_PAGER_OK); } return (vnode_pager_generic_getpages(vp, m, bytecount, reqpage, iodone, arg)); } /* * This is now called from local media FS's to operate against their * own vnodes if they fail to implement VOP_GETPAGES. */ int vnode_pager_generic_getpages(struct vnode *vp, vm_page_t *m, int bytecount, int reqpage, void (*iodone)(void *, vm_page_t *, int, int), void *arg) { vm_object_t object; off_t foff; int i, j, size, bsize, first; daddr_t firstaddr, reqblock; struct bufobj *bo; int runpg; int runend; struct buf *bp; int count; int error; object = vp->v_object; count = bytecount / PAGE_SIZE; KASSERT(vp->v_type != VCHR && vp->v_type != VBLK, ("vnode_pager_generic_getpages does not support devices")); if (vp->v_iflag & VI_DOOMED) return VM_PAGER_BAD; bsize = vp->v_mount->mnt_stat.f_iosize; foff = IDX_TO_OFF(m[reqpage]->pindex); /* * Get the underlying device blocks for the file with VOP_BMAP(). * If the file system doesn't support VOP_BMAP, use old way of * getting pages via VOP_READ. */ error = VOP_BMAP(vp, foff / bsize, &bo, &reqblock, NULL, NULL); if (error == EOPNOTSUPP) { VM_OBJECT_WLOCK(object); for (i = 0; i < count; i++) if (i != reqpage) { vm_page_lock(m[i]); vm_page_free(m[i]); vm_page_unlock(m[i]); } PCPU_INC(cnt.v_vnodein); PCPU_INC(cnt.v_vnodepgsin); error = vnode_pager_input_old(object, m[reqpage]); VM_OBJECT_WUNLOCK(object); return (error); } else if (error != 0) { vm_pager_free_nonreq(object, m, reqpage, count); return (VM_PAGER_ERROR); /* * if the blocksize is smaller than a page size, then use * special small filesystem code. NFS sometimes has a small * blocksize, but it can handle large reads itself. */ } else if ((PAGE_SIZE / bsize) > 1 && (vp->v_mount->mnt_stat.f_type != nfs_mount_type)) { vm_pager_free_nonreq(object, m, reqpage, count); PCPU_INC(cnt.v_vnodein); PCPU_INC(cnt.v_vnodepgsin); return vnode_pager_input_smlfs(object, m[reqpage]); } /* * Since the caller has busied the requested page, that page's valid * field will not be changed by other threads. */ vm_page_assert_xbusied(m[reqpage]); /* * If we have a completely valid page available to us, we can * clean up and return. Otherwise we have to re-read the * media. */ if (m[reqpage]->valid == VM_PAGE_BITS_ALL) { vm_pager_free_nonreq(object, m, reqpage, count); return (VM_PAGER_OK); } else if (reqblock == -1) { pmap_zero_page(m[reqpage]); KASSERT(m[reqpage]->dirty == 0, ("vnode_pager_generic_getpages: page %p is dirty", m)); VM_OBJECT_WLOCK(object); m[reqpage]->valid = VM_PAGE_BITS_ALL; for (i = 0; i < count; i++) if (i != reqpage) { vm_page_lock(m[i]); vm_page_free(m[i]); vm_page_unlock(m[i]); } VM_OBJECT_WUNLOCK(object); return (VM_PAGER_OK); } else if (m[reqpage]->valid != 0) { VM_OBJECT_WLOCK(object); m[reqpage]->valid = 0; VM_OBJECT_WUNLOCK(object); } /* * here on direct device I/O */ firstaddr = -1; /* * calculate the run that includes the required page */ for (first = 0, i = 0; i < count; i = runend) { if (vnode_pager_addr(vp, IDX_TO_OFF(m[i]->pindex), &firstaddr, &runpg) != 0) { VM_OBJECT_WLOCK(object); for (; i < count; i++) if (i != reqpage) { vm_page_lock(m[i]); vm_page_free(m[i]); vm_page_unlock(m[i]); } VM_OBJECT_WUNLOCK(object); return (VM_PAGER_ERROR); } if (firstaddr == -1) { VM_OBJECT_WLOCK(object); if (i == reqpage && foff < object->un_pager.vnp.vnp_size) { panic("vnode_pager_getpages: unexpected missing page: firstaddr: %jd, foff: 0x%jx%08jx, vnp_size: 0x%jx%08jx", (intmax_t)firstaddr, (uintmax_t)(foff >> 32), (uintmax_t)foff, (uintmax_t) (object->un_pager.vnp.vnp_size >> 32), (uintmax_t)object->un_pager.vnp.vnp_size); } vm_page_lock(m[i]); vm_page_free(m[i]); vm_page_unlock(m[i]); VM_OBJECT_WUNLOCK(object); runend = i + 1; first = runend; continue; } runend = i + runpg; if (runend <= reqpage) { VM_OBJECT_WLOCK(object); for (j = i; j < runend; j++) { vm_page_lock(m[j]); vm_page_free(m[j]); vm_page_unlock(m[j]); } VM_OBJECT_WUNLOCK(object); } else { if (runpg < (count - first)) { VM_OBJECT_WLOCK(object); for (i = first + runpg; i < count; i++) { vm_page_lock(m[i]); vm_page_free(m[i]); vm_page_unlock(m[i]); } VM_OBJECT_WUNLOCK(object); count = first + runpg; } break; } first = runend; } /* * the first and last page have been calculated now, move input pages * to be zero based... */ if (first != 0) { m += first; count -= first; reqpage -= first; } /* * calculate the file virtual address for the transfer */ foff = IDX_TO_OFF(m[0]->pindex); /* * calculate the size of the transfer */ size = count * PAGE_SIZE; KASSERT(count > 0, ("zero count")); if ((foff + size) > object->un_pager.vnp.vnp_size) size = object->un_pager.vnp.vnp_size - foff; KASSERT(size > 0, ("zero size")); /* * round up physical size for real devices. */ if (1) { int secmask = bo->bo_bsize - 1; KASSERT(secmask < PAGE_SIZE && secmask > 0, ("vnode_pager_generic_getpages: sector size %d too large", secmask + 1)); size = (size + secmask) & ~secmask; } bp = getpbuf(&vnode_pbuf_freecnt); bp->b_kvaalloc = bp->b_data; /* * and map the pages to be read into the kva, if the filesystem * requires mapped buffers. */ - if ((vp->v_mount->mnt_kern_flag & MNTK_UNMAPPED_BUFS) && + if ((vp->v_mount->mnt_kern_flag & MNTK_UNMAPPED_BUFS) != 0 && unmapped_buf_allowed) { bp->b_data = unmapped_buf; bp->b_kvabase = unmapped_buf; bp->b_offset = 0; bp->b_flags |= B_UNMAPPED; } else pmap_qenter((vm_offset_t)bp->b_kvaalloc, m, count); /* build a minimal buffer header */ bp->b_iocmd = BIO_READ; KASSERT(bp->b_rcred == NOCRED, ("leaking read ucred")); KASSERT(bp->b_wcred == NOCRED, ("leaking write ucred")); bp->b_rcred = crhold(curthread->td_ucred); bp->b_wcred = crhold(curthread->td_ucred); bp->b_blkno = firstaddr; pbgetbo(bo, bp); bp->b_vp = vp; bp->b_bcount = size; bp->b_bufsize = size; bp->b_runningbufspace = bp->b_bufsize; for (i = 0; i < count; i++) bp->b_pages[i] = m[i]; bp->b_npages = count; bp->b_pager.pg_reqpage = reqpage; atomic_add_long(&runningbufspace, bp->b_runningbufspace); PCPU_INC(cnt.v_vnodein); PCPU_ADD(cnt.v_vnodepgsin, count); /* do the input */ bp->b_iooffset = dbtob(bp->b_blkno); if (iodone) { /* async */ struct getpages_data *d; d = malloc(sizeof(*d), M_TEMP, M_WAITOK); d->iodone = iodone; d->arg = arg; bp->b_caller1 = d; bp->b_iodone = vnode_pager_generic_getpages_done_async; bp->b_flags |= B_ASYNC; BUF_KERNPROC(bp); bstrategy(bp); /* Good bye! */ } else { bp->b_iodone = bdone; bstrategy(bp); bwait(bp, PVM, "vnread"); error = vnode_pager_generic_getpages_done(bp); for (int i = 0; i < bp->b_npages; i++) bp->b_pages[i] = NULL; bp->b_vp = NULL; pbrelbo(bp); relpbuf(bp, &vnode_pbuf_freecnt); } return (error ? VM_PAGER_ERROR : VM_PAGER_OK); } static void vnode_pager_generic_getpages_done_async(struct buf *bp) { struct getpages_data *d = bp->b_caller1; int error; error = vnode_pager_generic_getpages_done(bp); d->iodone(d->arg, bp->b_pages, bp->b_pager.pg_reqpage, error); for (int i = 0; i < bp->b_npages; i++) bp->b_pages[i] = NULL; bp->b_vp = NULL; pbrelbo(bp); relpbuf(bp, &vnode_pbuf_freecnt); free(d, M_TEMP); } static int vnode_pager_generic_getpages_done(struct buf *bp) { vm_object_t object = bp->b_vp->v_object; off_t tfoff, nextoff; int i, error; if ((bp->b_ioflags & BIO_ERROR) != 0) error = EIO; else error = 0; if (error == 0 && bp->b_bcount != bp->b_npages * PAGE_SIZE) { if ((bp->b_flags & B_UNMAPPED) != 0) { bp->b_flags &= ~B_UNMAPPED; pmap_qenter((vm_offset_t)bp->b_kvaalloc, bp->b_pages, bp->b_npages); } bzero(bp->b_kvaalloc + bp->b_bcount, PAGE_SIZE * bp->b_npages - bp->b_bcount); } if ((bp->b_flags & B_UNMAPPED) == 0) pmap_qremove((vm_offset_t)bp->b_kvaalloc, bp->b_npages); if ((bp->b_vp->v_mount->mnt_kern_flag & MNTK_UNMAPPED_BUFS) != 0) { bp->b_data = bp->b_kvaalloc; bp->b_kvabase = bp->b_kvaalloc; bp->b_flags &= ~B_UNMAPPED; } VM_OBJECT_WLOCK(object); for (i = 0, tfoff = IDX_TO_OFF(bp->b_pages[0]->pindex); i < bp->b_npages; i++, tfoff = nextoff) { vm_page_t mt; nextoff = tfoff + PAGE_SIZE; mt = bp->b_pages[i]; if (nextoff <= object->un_pager.vnp.vnp_size) { /* * Read filled up entire page. */ mt->valid = VM_PAGE_BITS_ALL; KASSERT(mt->dirty == 0, ("%s: page %p is dirty", __func__, mt)); KASSERT(!pmap_page_is_mapped(mt), ("%s: page %p is mapped", __func__, mt)); } else { /* * Read did not fill up entire page. * * Currently we do not set the entire page valid, * we just try to clear the piece that we couldn't * read. */ vm_page_set_valid_range(mt, 0, object->un_pager.vnp.vnp_size - tfoff); KASSERT((mt->dirty & vm_page_bits(0, object->un_pager.vnp.vnp_size - tfoff)) == 0, ("%s: page %p is dirty", __func__, mt)); } if (i != bp->b_pager.pg_reqpage) vm_page_readahead_finish(mt); } VM_OBJECT_WUNLOCK(object); if (error) { printf("vnode_pager_getpages: I/O read error\n"); } return (error); } /* * EOPNOTSUPP is no longer legal. For local media VFS's that do not * implement their own VOP_PUTPAGES, their VOP_PUTPAGES should call to * vnode_pager_generic_putpages() to implement the previous behaviour. * * All other FS's should use the bypass to get to the local media * backing vp's VOP_PUTPAGES. */ static void vnode_pager_putpages(vm_object_t object, vm_page_t *m, int count, int flags, int *rtvals) { int rtval; struct vnode *vp; int bytes = count * PAGE_SIZE; /* * Force synchronous operation if we are extremely low on memory * to prevent a low-memory deadlock. VOP operations often need to * allocate more memory to initiate the I/O ( i.e. do a BMAP * operation ). The swapper handles the case by limiting the amount * of asynchronous I/O, but that sort of solution doesn't scale well * for the vnode pager without a lot of work. * * Also, the backing vnode's iodone routine may not wake the pageout * daemon up. This should be probably be addressed XXX. */ if (vm_cnt.v_free_count + vm_cnt.v_cache_count < vm_cnt.v_pageout_free_min) flags |= VM_PAGER_PUT_SYNC; /* * Call device-specific putpages function */ vp = object->handle; VM_OBJECT_WUNLOCK(object); rtval = VOP_PUTPAGES(vp, m, bytes, flags, rtvals); KASSERT(rtval != EOPNOTSUPP, ("vnode_pager: stale FS putpages\n")); VM_OBJECT_WLOCK(object); } /* * This is now called from local media FS's to operate against their * own vnodes if they fail to implement VOP_PUTPAGES. * * This is typically called indirectly via the pageout daemon and * clustering has already typically occured, so in general we ask the * underlying filesystem to write the data out asynchronously rather * then delayed. */ int vnode_pager_generic_putpages(struct vnode *vp, vm_page_t *ma, int bytecount, int flags, int *rtvals) { int i; vm_object_t object; vm_page_t m; int count; int maxsize, ncount; vm_ooffset_t poffset; struct uio auio; struct iovec aiov; int error; int ioflags; int ppscheck = 0; static struct timeval lastfail; static int curfail; object = vp->v_object; count = bytecount / PAGE_SIZE; for (i = 0; i < count; i++) rtvals[i] = VM_PAGER_ERROR; if ((int64_t)ma[0]->pindex < 0) { printf("vnode_pager_putpages: attempt to write meta-data!!! -- 0x%lx(%lx)\n", (long)ma[0]->pindex, (u_long)ma[0]->dirty); rtvals[0] = VM_PAGER_BAD; return VM_PAGER_BAD; } maxsize = count * PAGE_SIZE; ncount = count; poffset = IDX_TO_OFF(ma[0]->pindex); /* * If the page-aligned write is larger then the actual file we * have to invalidate pages occuring beyond the file EOF. However, * there is an edge case where a file may not be page-aligned where * the last page is partially invalid. In this case the filesystem * may not properly clear the dirty bits for the entire page (which * could be VM_PAGE_BITS_ALL due to the page having been mmap()d). * With the page locked we are free to fix-up the dirty bits here. * * We do not under any circumstances truncate the valid bits, as * this will screw up bogus page replacement. */ VM_OBJECT_WLOCK(object); if (maxsize + poffset > object->un_pager.vnp.vnp_size) { if (object->un_pager.vnp.vnp_size > poffset) { int pgoff; maxsize = object->un_pager.vnp.vnp_size - poffset; ncount = btoc(maxsize); if ((pgoff = (int)maxsize & PAGE_MASK) != 0) { /* * If the object is locked and the following * conditions hold, then the page's dirty * field cannot be concurrently changed by a * pmap operation. */ m = ma[ncount - 1]; vm_page_assert_sbusied(m); KASSERT(!pmap_page_is_write_mapped(m), ("vnode_pager_generic_putpages: page %p is not read-only", m)); vm_page_clear_dirty(m, pgoff, PAGE_SIZE - pgoff); } } else { maxsize = 0; ncount = 0; } if (ncount < count) { for (i = ncount; i < count; i++) { rtvals[i] = VM_PAGER_BAD; } } } VM_OBJECT_WUNLOCK(object); /* * pageouts are already clustered, use IO_ASYNC to force a bawrite() * rather then a bdwrite() to prevent paging I/O from saturating * the buffer cache. Dummy-up the sequential heuristic to cause * large ranges to cluster. If neither IO_SYNC or IO_ASYNC is set, * the system decides how to cluster. */ ioflags = IO_VMIO; if (flags & (VM_PAGER_PUT_SYNC | VM_PAGER_PUT_INVAL)) ioflags |= IO_SYNC; else if ((flags & VM_PAGER_CLUSTER_OK) == 0) ioflags |= IO_ASYNC; ioflags |= (flags & VM_PAGER_PUT_INVAL) ? IO_INVAL: 0; ioflags |= IO_SEQMAX << IO_SEQSHIFT; aiov.iov_base = (caddr_t) 0; aiov.iov_len = maxsize; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; auio.uio_offset = poffset; auio.uio_segflg = UIO_NOCOPY; auio.uio_rw = UIO_WRITE; auio.uio_resid = maxsize; auio.uio_td = (struct thread *) 0; error = VOP_WRITE(vp, &auio, ioflags, curthread->td_ucred); PCPU_INC(cnt.v_vnodeout); PCPU_ADD(cnt.v_vnodepgsout, ncount); if (error) { if ((ppscheck = ppsratecheck(&lastfail, &curfail, 1))) printf("vnode_pager_putpages: I/O error %d\n", error); } if (auio.uio_resid) { if (ppscheck || ppsratecheck(&lastfail, &curfail, 1)) printf("vnode_pager_putpages: residual I/O %zd at %lu\n", auio.uio_resid, (u_long)ma[0]->pindex); } for (i = 0; i < ncount; i++) { rtvals[i] = VM_PAGER_OK; } return rtvals[0]; } void vnode_pager_undirty_pages(vm_page_t *ma, int *rtvals, int written) { vm_object_t obj; int i, pos; if (written == 0) return; obj = ma[0]->object; VM_OBJECT_WLOCK(obj); for (i = 0, pos = 0; pos < written; i++, pos += PAGE_SIZE) { if (pos < trunc_page(written)) { rtvals[i] = VM_PAGER_OK; vm_page_undirty(ma[i]); } else { /* Partially written page. */ rtvals[i] = VM_PAGER_AGAIN; vm_page_clear_dirty(ma[i], 0, written & PAGE_MASK); } } VM_OBJECT_WUNLOCK(obj); } void vnode_pager_update_writecount(vm_object_t object, vm_offset_t start, vm_offset_t end) { struct vnode *vp; vm_ooffset_t old_wm; VM_OBJECT_WLOCK(object); if (object->type != OBJT_VNODE) { VM_OBJECT_WUNLOCK(object); return; } old_wm = object->un_pager.vnp.writemappings; object->un_pager.vnp.writemappings += (vm_ooffset_t)end - start; vp = object->handle; if (old_wm == 0 && object->un_pager.vnp.writemappings != 0) { ASSERT_VOP_ELOCKED(vp, "v_writecount inc"); VOP_ADD_WRITECOUNT(vp, 1); CTR3(KTR_VFS, "%s: vp %p v_writecount increased to %d", __func__, vp, vp->v_writecount); } else if (old_wm != 0 && object->un_pager.vnp.writemappings == 0) { ASSERT_VOP_ELOCKED(vp, "v_writecount dec"); VOP_ADD_WRITECOUNT(vp, -1); CTR3(KTR_VFS, "%s: vp %p v_writecount decreased to %d", __func__, vp, vp->v_writecount); } VM_OBJECT_WUNLOCK(object); } void vnode_pager_release_writecount(vm_object_t object, vm_offset_t start, vm_offset_t end) { struct vnode *vp; struct mount *mp; vm_offset_t inc; VM_OBJECT_WLOCK(object); /* * First, recheck the object type to account for the race when * the vnode is reclaimed. */ if (object->type != OBJT_VNODE) { VM_OBJECT_WUNLOCK(object); return; } /* * Optimize for the case when writemappings is not going to * zero. */ inc = end - start; if (object->un_pager.vnp.writemappings != inc) { object->un_pager.vnp.writemappings -= inc; VM_OBJECT_WUNLOCK(object); return; } vp = object->handle; vhold(vp); VM_OBJECT_WUNLOCK(object); mp = NULL; vn_start_write(vp, &mp, V_WAIT); vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); /* * Decrement the object's writemappings, by swapping the start * and end arguments for vnode_pager_update_writecount(). If * there was not a race with vnode reclaimation, then the * vnode's v_writecount is decremented. */ vnode_pager_update_writecount(object, end, start); VOP_UNLOCK(vp, 0); vdrop(vp); if (mp != NULL) vn_finished_write(mp); } Index: projects/sendfile/sys =================================================================== --- projects/sendfile/sys (revision 274716) +++ projects/sendfile/sys (revision 274717) Property changes on: projects/sendfile/sys ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head/sys:r274690-274716 Index: projects/sendfile =================================================================== --- projects/sendfile (revision 274716) +++ projects/sendfile (revision 274717) Property changes on: projects/sendfile ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r274690-274716