/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Fibre Channel SCSI ULP Mapping driver */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Discovery Process * ================= * * The discovery process is a major function of FCP. In order to help * understand that function a flow diagram is given here. This diagram * doesn't claim to cover all the cases and the events that can occur during * the discovery process nor the subtleties of the code. The code paths shown * are simplified. Its purpose is to help the reader (and potentially bug * fixer) have an overall view of the logic of the code. For that reason the * diagram covers the simple case of the line coming up cleanly or of a new * port attaching to FCP the link being up. The reader must keep in mind * that: * * - There are special cases where bringing devices online and offline * is driven by Ioctl. * * - The behavior of the discovery process can be modified through the * .conf file. * * - The line can go down and come back up at any time during the * discovery process which explains some of the complexity of the code. * * ............................................................................ * * STEP 1: The line comes up or a new Fibre Channel port attaches to FCP. * * * +-------------------------+ * fp/fctl module --->| fcp_port_attach | * +-------------------------+ * | | * | | * | v * | +-------------------------+ * | | fcp_handle_port_attach | * | +-------------------------+ * | | * | | * +--------------------+ | * | | * v v * +-------------------------+ * | fcp_statec_callback | * +-------------------------+ * | * | * v * +-------------------------+ * | fcp_handle_devices | * +-------------------------+ * | * | * v * +-------------------------+ * | fcp_handle_mapflags | * +-------------------------+ * | * | * v * +-------------------------+ * | fcp_send_els | * | | * | PLOGI or PRLI To all the| * | reachable devices. | * +-------------------------+ * * * ............................................................................ * * STEP 2: The callback functions of the PLOGI and/or PRLI requests sent during * STEP 1 are called (it is actually the same function). * * * +-------------------------+ * | fcp_icmd_callback | * fp/fctl module --->| | * | callback for PLOGI and | * | PRLI. | * +-------------------------+ * | * | * Received PLOGI Accept /-\ Received PRLI Accept * _ _ _ _ _ _ / \_ _ _ _ _ _ * | \ / | * | \-/ | * | | * v v * +-------------------------+ +-------------------------+ * | fcp_send_els | | fcp_send_scsi | * | | | | * | PRLI | | REPORT_LUN | * +-------------------------+ +-------------------------+ * * ............................................................................ * * STEP 3: The callback functions of the SCSI commands issued by FCP are called * (It is actually the same function). * * * +-------------------------+ * fp/fctl module ------->| fcp_scsi_callback | * +-------------------------+ * | * | * | * Receive REPORT_LUN reply /-\ Receive INQUIRY PAGE83 reply * _ _ _ _ _ _ _ _ _ _ / \_ _ _ _ _ _ _ _ _ _ _ _ * | \ / | * | \-/ | * | | | * | Receive INQUIRY reply| | * | | | * v v v * +------------------------+ +----------------------+ +----------------------+ * | fcp_handle_reportlun | | fcp_handle_inquiry | | fcp_handle_page83 | * |(Called for each Target)| | (Called for each LUN)| |(Called for each LUN) | * +------------------------+ +----------------------+ +----------------------+ * | | | * | | | * | | | * v v | * +-----------------+ +-----------------+ | * | fcp_send_scsi | | fcp_send_scsi | | * | | | | | * | INQUIRY | | INQUIRY PAGE83 | | * | (To each LUN) | +-----------------+ | * +-----------------+ | * | * v * +------------------------+ * | fcp_call_finish_init | * +------------------------+ * | * v * +-----------------------------+ * | fcp_call_finish_init_held | * +-----------------------------+ * | * | * All LUNs scanned /-\ * _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ / \ * | \ / * | \-/ * v | * +------------------+ | * | fcp_finish_tgt | | * +------------------+ | * | Target Not Offline and | * Target Not Offline and | not marked and tgt_node_state | * marked /-\ not FCP_TGT_NODE_ON_DEMAND | * _ _ _ _ _ _ / \_ _ _ _ _ _ _ _ | * | \ / | | * | \-/ | | * v v | * +----------------------------+ +-------------------+ | * | fcp_offline_target | | fcp_create_luns | | * | | +-------------------+ | * | A structure fcp_tgt_elem | | | * | is created and queued in | v | * | the FCP port list | +-------------------+ | * | port_offline_tgts. It | | fcp_pass_to_hp | | * | will be unqueued by the | | | | * | watchdog timer. | | Called for each | | * +----------------------------+ | LUN. Dispatches | | * | | fcp_hp_task | | * | +-------------------+ | * | | | * | | | * | | | * | +---------------->| * | | * +---------------------------------------------->| * | * | * All the targets (devices) have been scanned /-\ * _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ / \ * | \ / * | \-/ * +-------------------------------------+ | * | fcp_finish_init | | * | | | * | Signal broadcasts the condition | | * | variable port_config_cv of the FCP | | * | port. One potential code sequence | | * | waiting on the condition variable | | * | the code sequence handling | | * | BUS_CONFIG_ALL and BUS_CONFIG_DRIVER| | * | The other is in the function | | * | fcp_reconfig_wait which is called | | * | in the transmit path preventing IOs | | * | from going through till the disco- | | * | very process is over. | | * +-------------------------------------+ | * | | * | | * +--------------------------------->| * | * v * Return * * ............................................................................ * * STEP 4: The hot plug task is called (for each fcp_hp_elem). * * * +-------------------------+ * | fcp_hp_task | * +-------------------------+ * | * | * v * +-------------------------+ * | fcp_trigger_lun | * +-------------------------+ * | * | * v * Bring offline /-\ Bring online * _ _ _ _ _ _ _ _ _/ \_ _ _ _ _ _ _ _ _ _ * | \ / | * | \-/ | * v v * +---------------------+ +-----------------------+ * | fcp_offline_child | | fcp_get_cip | * +---------------------+ | | * | Creates a dev_info_t | * | or a mdi_pathinfo_t | * | depending on whether | * | mpxio is on or off. | * +-----------------------+ * | * | * v * +-----------------------+ * | fcp_online_child | * | | * | Set device online | * | using NDI or MDI. | * +-----------------------+ * * ............................................................................ * * STEP 5: The watchdog timer expires. The watch dog timer does much more that * what is described here. We only show the target offline path. * * * +--------------------------+ * | fcp_watch | * +--------------------------+ * | * | * v * +--------------------------+ * | fcp_scan_offline_tgts | * +--------------------------+ * | * | * v * +--------------------------+ * | fcp_offline_target_now | * +--------------------------+ * | * | * v * +--------------------------+ * | fcp_offline_tgt_luns | * +--------------------------+ * | * | * v * +--------------------------+ * | fcp_offline_lun | * +--------------------------+ * | * | * v * +----------------------------------+ * | fcp_offline_lun_now | * | | * | A request (or two if mpxio) is | * | sent to the hot plug task using | * | a fcp_hp_elem structure. | * +----------------------------------+ */ /* * Functions registered with DDI framework */ static int fcp_attach(dev_info_t *devi, ddi_attach_cmd_t cmd); static int fcp_detach(dev_info_t *devi, ddi_detach_cmd_t cmd); static int fcp_open(dev_t *devp, int flag, int otype, cred_t *credp); static int fcp_close(dev_t dev, int flag, int otype, cred_t *credp); static int fcp_ioctl(dev_t dev, int cmd, intptr_t data, int mode, cred_t *credp, int *rval); /* * Functions registered with FC Transport framework */ static int fcp_port_attach(opaque_t ulph, fc_ulp_port_info_t *pinfo, fc_attach_cmd_t cmd, uint32_t s_id); static int fcp_port_detach(opaque_t ulph, fc_ulp_port_info_t *info, fc_detach_cmd_t cmd); static int fcp_port_ioctl(opaque_t ulph, opaque_t port_handle, dev_t dev, int cmd, intptr_t data, int mode, cred_t *credp, int *rval, uint32_t claimed); static int fcp_els_callback(opaque_t ulph, opaque_t port_handle, fc_unsol_buf_t *buf, uint32_t claimed); static int fcp_data_callback(opaque_t ulph, opaque_t port_handle, fc_unsol_buf_t *buf, uint32_t claimed); static void fcp_statec_callback(opaque_t ulph, opaque_t port_handle, uint32_t port_state, uint32_t port_top, fc_portmap_t *devlist, uint32_t dev_cnt, uint32_t port_sid); /* * Functions registered with SCSA framework */ static int fcp_phys_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *hba_tran, struct scsi_device *sd); static int fcp_scsi_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *hba_tran, struct scsi_device *sd); static void fcp_scsi_tgt_free(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *hba_tran, struct scsi_device *sd); static int fcp_scsi_start(struct scsi_address *ap, struct scsi_pkt *pkt); static int fcp_scsi_abort(struct scsi_address *ap, struct scsi_pkt *pkt); static int fcp_scsi_reset(struct scsi_address *ap, int level); static int fcp_scsi_getcap(struct scsi_address *ap, char *cap, int whom); static int fcp_scsi_setcap(struct scsi_address *ap, char *cap, int value, int whom); static void fcp_pkt_teardown(struct scsi_pkt *pkt); static int fcp_scsi_reset_notify(struct scsi_address *ap, int flag, void (*callback)(caddr_t), caddr_t arg); static int fcp_scsi_bus_get_eventcookie(dev_info_t *dip, dev_info_t *rdip, char *name, ddi_eventcookie_t *event_cookiep); static int fcp_scsi_bus_add_eventcall(dev_info_t *dip, dev_info_t *rdip, ddi_eventcookie_t eventid, void (*callback)(), void *arg, ddi_callback_id_t *cb_id); static int fcp_scsi_bus_remove_eventcall(dev_info_t *devi, ddi_callback_id_t cb_id); static int fcp_scsi_bus_post_event(dev_info_t *dip, dev_info_t *rdip, ddi_eventcookie_t eventid, void *impldata); static int fcp_scsi_bus_config(dev_info_t *parent, uint_t flag, ddi_bus_config_op_t op, void *arg, dev_info_t **childp); static int fcp_scsi_bus_unconfig(dev_info_t *parent, uint_t flag, ddi_bus_config_op_t op, void *arg); /* * Internal functions */ static int fcp_setup_device_data_ioctl(int cmd, struct fcp_ioctl *data, int mode, int *rval); static int fcp_setup_scsi_ioctl(struct fcp_scsi_cmd *u_fscsi, int mode, int *rval); static int fcp_copyin_scsi_cmd(caddr_t base_addr, struct fcp_scsi_cmd *fscsi, int mode); static int fcp_copyout_scsi_cmd(struct fcp_scsi_cmd *fscsi, caddr_t base_addr, int mode); static int fcp_send_scsi_ioctl(struct fcp_scsi_cmd *fscsi); static struct fcp_tgt *fcp_port_create_tgt(struct fcp_port *pptr, la_wwn_t *pwwn, int *ret_val, int *fc_status, int *fc_pkt_state, int *fc_pkt_reason, int *fc_pkt_action); static int fcp_tgt_send_plogi(struct fcp_tgt *ptgt, int *fc_status, int *fc_pkt_state, int *fc_pkt_reason, int *fc_pkt_action); static int fcp_tgt_send_prli(struct fcp_tgt *ptgt, int *fc_status, int *fc_pkt_state, int *fc_pkt_reason, int *fc_pkt_action); static void fcp_ipkt_sema_init(struct fcp_ipkt *icmd); static int fcp_ipkt_sema_wait(struct fcp_ipkt *icmd); static void fcp_ipkt_sema_callback(struct fc_packet *fpkt); static void fcp_ipkt_sema_cleanup(struct fcp_ipkt *icmd); static void fcp_handle_devices(struct fcp_port *pptr, fc_portmap_t devlist[], uint32_t dev_cnt, int link_cnt, fcp_map_tag_t *map_tag, int cause); static int fcp_handle_mapflags(struct fcp_port *pptr, struct fcp_tgt *ptgt, fc_portmap_t *map_entry, int link_cnt, int tgt_cnt, int cause); static int fcp_send_els(struct fcp_port *pptr, struct fcp_tgt *ptgt, struct fcp_ipkt *icmd, uchar_t opcode, int lcount, int tcount, int cause); static void fcp_update_state(struct fcp_port *pptr, uint32_t state, int cause); static void fcp_update_tgt_state(struct fcp_tgt *ptgt, int flag, uint32_t state); static struct fcp_port *fcp_get_port(opaque_t port_handle); static void fcp_unsol_callback(fc_packet_t *fpkt); static void fcp_unsol_resp_init(fc_packet_t *pkt, fc_unsol_buf_t *buf, uchar_t r_ctl, uchar_t type); static int fcp_unsol_prli(struct fcp_port *pptr, fc_unsol_buf_t *buf); static struct fcp_ipkt *fcp_icmd_alloc(struct fcp_port *pptr, struct fcp_tgt *ptgt, int cmd_len, int resp_len, int data_len, int nodma, int lcount, int tcount, int cause, uint32_t rscn_count); static void fcp_icmd_free(struct fcp_port *pptr, struct fcp_ipkt *icmd); static int fcp_alloc_dma(struct fcp_port *pptr, struct fcp_ipkt *icmd, int nodma, int flags); static void fcp_free_dma(struct fcp_port *pptr, struct fcp_ipkt *icmd); static struct fcp_tgt *fcp_lookup_target(struct fcp_port *pptr, uchar_t *wwn); static struct fcp_tgt *fcp_get_target_by_did(struct fcp_port *pptr, uint32_t d_id); static void fcp_icmd_callback(fc_packet_t *fpkt); static int fcp_send_scsi(struct fcp_lun *plun, uchar_t opcode, int len, int lcount, int tcount, int cause, uint32_t rscn_count); static int fcp_check_reportlun(struct fcp_rsp *rsp, fc_packet_t *fpkt); static void fcp_scsi_callback(fc_packet_t *fpkt); static void fcp_retry_scsi_cmd(fc_packet_t *fpkt); static void fcp_handle_inquiry(fc_packet_t *fpkt, struct fcp_ipkt *icmd); static void fcp_handle_reportlun(fc_packet_t *fpkt, struct fcp_ipkt *icmd); static struct fcp_lun *fcp_get_lun(struct fcp_tgt *ptgt, uint16_t lun_num); static int fcp_finish_tgt(struct fcp_port *pptr, struct fcp_tgt *ptgt, int link_cnt, int tgt_cnt, int cause); static void fcp_finish_init(struct fcp_port *pptr); static void fcp_create_luns(struct fcp_tgt *ptgt, int link_cnt, int tgt_cnt, int cause); static int fcp_trigger_lun(struct fcp_lun *plun, child_info_t *cip, int old_mpxio, int online, int link_cnt, int tgt_cnt, int flags); static int fcp_offline_target(struct fcp_port *pptr, struct fcp_tgt *ptgt, int link_cnt, int tgt_cnt, int nowait, int flags); static void fcp_offline_target_now(struct fcp_port *pptr, struct fcp_tgt *ptgt, int link_cnt, int tgt_cnt, int flags); static void fcp_offline_tgt_luns(struct fcp_tgt *ptgt, int link_cnt, int tgt_cnt, int flags); static void fcp_offline_lun(struct fcp_lun *plun, int link_cnt, int tgt_cnt, int nowait, int flags); static void fcp_prepare_offline_lun(struct fcp_lun *plun, int link_cnt, int tgt_cnt); static void fcp_offline_lun_now(struct fcp_lun *plun, int link_cnt, int tgt_cnt, int flags); static void fcp_scan_offline_luns(struct fcp_port *pptr); static void fcp_scan_offline_tgts(struct fcp_port *pptr); static void fcp_update_offline_flags(struct fcp_lun *plun); static struct fcp_pkt *fcp_scan_commands(struct fcp_lun *plun); static void fcp_abort_commands(struct fcp_pkt *head, struct fcp_port *pptr); static void fcp_cmd_callback(fc_packet_t *fpkt); static void fcp_complete_pkt(fc_packet_t *fpkt); static int fcp_validate_fcp_response(struct fcp_rsp *rsp, struct fcp_port *pptr); static int fcp_device_changed(struct fcp_port *pptr, struct fcp_tgt *ptgt, fc_portmap_t *map_entry, int link_cnt, int tgt_cnt, int cause); static struct fcp_lun *fcp_alloc_lun(struct fcp_tgt *ptgt); static void fcp_dealloc_lun(struct fcp_lun *plun); static struct fcp_tgt *fcp_alloc_tgt(struct fcp_port *pptr, fc_portmap_t *map_entry, int link_cnt); static void fcp_dealloc_tgt(struct fcp_tgt *ptgt); static void fcp_queue_ipkt(struct fcp_port *pptr, fc_packet_t *fpkt); static int fcp_transport(opaque_t port_handle, fc_packet_t *fpkt, int internal); static void fcp_log(int level, dev_info_t *dip, const char *fmt, ...); static int fcp_handle_port_attach(opaque_t ulph, fc_ulp_port_info_t *pinfo, uint32_t s_id, int instance); static int fcp_handle_port_detach(struct fcp_port *pptr, int flag, int instance); static void fcp_cleanup_port(struct fcp_port *pptr, int instance); static int fcp_kmem_cache_constructor(struct scsi_pkt *, scsi_hba_tran_t *, int); static void fcp_kmem_cache_destructor(struct scsi_pkt *, scsi_hba_tran_t *); static int fcp_pkt_setup(struct scsi_pkt *, int (*)(), caddr_t); static int fcp_alloc_cmd_resp(struct fcp_port *pptr, fc_packet_t *fpkt, int flags); static void fcp_free_cmd_resp(struct fcp_port *pptr, fc_packet_t *fpkt); static int fcp_reset_target(struct scsi_address *ap, int level); static int fcp_commoncap(struct scsi_address *ap, char *cap, int val, int tgtonly, int doset); static int fcp_scsi_get_name(struct scsi_device *sd, char *name, int len); static int fcp_scsi_get_bus_addr(struct scsi_device *sd, char *name, int len); static int fcp_linkreset(struct fcp_port *pptr, struct scsi_address *ap, int sleep); static int fcp_handle_port_resume(opaque_t ulph, fc_ulp_port_info_t *pinfo, uint32_t s_id, fc_attach_cmd_t cmd, int instance); static void fcp_cp_pinfo(struct fcp_port *pptr, fc_ulp_port_info_t *pinfo); static void fcp_process_elem(struct fcp_hp_elem *elem, int result); static child_info_t *fcp_get_cip(struct fcp_lun *plun, child_info_t *cip, int lcount, int tcount); static int fcp_is_dip_present(struct fcp_lun *plun, dev_info_t *cdip); static int fcp_is_child_present(struct fcp_lun *plun, child_info_t *cip); static dev_info_t *fcp_create_dip(struct fcp_lun *plun, int link_cnt, int tgt_cnt); static dev_info_t *fcp_find_existing_dip(struct fcp_lun *plun, dev_info_t *pdip, caddr_t name); static int fcp_online_child(struct fcp_lun *plun, child_info_t *cip, int lcount, int tcount, int flags, int *circ); static int fcp_offline_child(struct fcp_lun *plun, child_info_t *cip, int lcount, int tcount, int flags, int *circ); static void fcp_remove_child(struct fcp_lun *plun); static void fcp_watch(void *arg); static void fcp_check_reset_delay(struct fcp_port *pptr); static void fcp_abort_all(struct fcp_port *pptr, struct fcp_tgt *ttgt, struct fcp_lun *rlun, int tgt_cnt); struct fcp_port *fcp_soft_state_unlink(struct fcp_port *pptr); static struct fcp_lun *fcp_lookup_lun(struct fcp_port *pptr, uchar_t *wwn, uint16_t lun); static void fcp_prepare_pkt(struct fcp_port *pptr, struct fcp_pkt *cmd, struct fcp_lun *plun); static void fcp_post_callback(struct fcp_pkt *cmd); static int fcp_dopoll(struct fcp_port *pptr, struct fcp_pkt *cmd); static struct fcp_port *fcp_dip2port(dev_info_t *dip); struct fcp_lun *fcp_get_lun_from_cip(struct fcp_port *pptr, child_info_t *cip); static int fcp_pass_to_hp_and_wait(struct fcp_port *pptr, struct fcp_lun *plun, child_info_t *cip, int what, int link_cnt, int tgt_cnt, int flags); static struct fcp_hp_elem *fcp_pass_to_hp(struct fcp_port *pptr, struct fcp_lun *plun, child_info_t *cip, int what, int link_cnt, int tgt_cnt, int flags, int wait); static void fcp_retransport_cmd(struct fcp_port *pptr, struct fcp_pkt *cmd); static void fcp_fail_cmd(struct fcp_pkt *cmd, uchar_t reason, uint_t statistics); static void fcp_queue_pkt(struct fcp_port *pptr, struct fcp_pkt *cmd); static void fcp_update_targets(struct fcp_port *pptr, fc_portmap_t *dev_list, uint32_t count, uint32_t state, int cause); static int fcp_call_finish_init(struct fcp_port *pptr, struct fcp_tgt *ptgt, int lcount, int tcount, int cause); static int fcp_call_finish_init_held(struct fcp_port *pptr, struct fcp_tgt *ptgt, int lcount, int tcount, int cause); static void fcp_reconfigure_luns(void * tgt_handle); static void fcp_free_targets(struct fcp_port *pptr); static void fcp_free_target(struct fcp_tgt *ptgt); static int fcp_is_retryable(struct fcp_ipkt *icmd); static int fcp_create_on_demand(struct fcp_port *pptr, uchar_t *pwwn); static void fcp_ascii_to_wwn(caddr_t string, uchar_t bytes[], unsigned int); static void fcp_wwn_to_ascii(uchar_t bytes[], char *string); static void fcp_print_error(fc_packet_t *fpkt); static int fcp_handle_ipkt_errors(struct fcp_port *pptr, struct fcp_tgt *ptgt, struct fcp_ipkt *icmd, int rval, caddr_t op); static int fcp_outstanding_lun_cmds(struct fcp_tgt *ptgt); static fc_portmap_t *fcp_construct_map(struct fcp_port *pptr, uint32_t *dev_cnt); static void fcp_offline_all(struct fcp_port *pptr, int lcount, int cause); static int fcp_get_statec_count(struct fcp_ioctl *data, int mode, int *rval); static int fcp_copyin_fcp_ioctl_data(struct fcp_ioctl *, int, int *, struct fcp_ioctl *, struct fcp_port **); static char *fcp_get_lun_path(struct fcp_lun *plun); static int fcp_get_target_mappings(struct fcp_ioctl *data, int mode, int *rval); static int fcp_do_ns_registry(struct fcp_port *pptr, uint32_t s_id); static void fcp_retry_ns_registry(struct fcp_port *pptr, uint32_t s_id); static char *fcp_get_lun_path(struct fcp_lun *plun); static int fcp_get_target_mappings(struct fcp_ioctl *data, int mode, int *rval); static void fcp_reconfig_wait(struct fcp_port *pptr); /* * New functions added for mpxio support */ static int fcp_virt_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *hba_tran, struct scsi_device *sd); static mdi_pathinfo_t *fcp_create_pip(struct fcp_lun *plun, int lcount, int tcount); static mdi_pathinfo_t *fcp_find_existing_pip(struct fcp_lun *plun, dev_info_t *pdip); static int fcp_is_pip_present(struct fcp_lun *plun, mdi_pathinfo_t *pip); static void fcp_handle_page83(fc_packet_t *, struct fcp_ipkt *, int); static void fcp_update_mpxio_path_verifybusy(struct fcp_port *pptr); static int fcp_copy_guid_2_lun_block(struct fcp_lun *plun, char *guidp); static int fcp_update_mpxio_path(struct fcp_lun *plun, child_info_t *cip, int what); static int fcp_is_reconfig_needed(struct fcp_tgt *ptgt, fc_packet_t *fpkt); static int fcp_symmetric_device_probe(struct fcp_lun *plun); /* * New functions added for lun masking support */ static void fcp_read_blacklist(dev_info_t *dip, struct fcp_black_list_entry **pplun_blacklist); static void fcp_mask_pwwn_lun(char *curr_pwwn, char *curr_lun, struct fcp_black_list_entry **pplun_blacklist); static void fcp_add_one_mask(char *curr_pwwn, uint32_t lun_id, struct fcp_black_list_entry **pplun_blacklist); static int fcp_should_mask(la_wwn_t *wwn, uint32_t lun_id); static void fcp_cleanup_blacklist(struct fcp_black_list_entry **lun_blacklist); extern struct mod_ops mod_driverops; /* * This variable is defined in modctl.c and set to '1' after the root driver * and fs are loaded. It serves as an indication that the root filesystem can * be used. */ extern int modrootloaded; /* * This table contains strings associated with the SCSI sense key codes. It * is used by FCP to print a clear explanation of the code returned in the * sense information by a device. */ extern char *sense_keys[]; /* * This device is created by the SCSI pseudo nexus driver (SCSI vHCI). It is * under this device that the paths to a physical device are created when * MPxIO is used. */ extern dev_info_t *scsi_vhci_dip; /* * Report lun processing */ #define FCP_LUN_ADDRESSING 0x80 #define FCP_PD_ADDRESSING 0x00 #define FCP_VOLUME_ADDRESSING 0x40 #define FCP_SVE_THROTTLE 0x28 /* Vicom */ #define MAX_INT_DMA 0x7fffffff #define FCP_MAX_SENSE_LEN 252 #define FCP_MAX_RESPONSE_LEN 0xffffff /* * Property definitions */ #define NODE_WWN_PROP (char *)fcp_node_wwn_prop #define PORT_WWN_PROP (char *)fcp_port_wwn_prop #define TARGET_PROP (char *)fcp_target_prop #define LUN_PROP (char *)fcp_lun_prop #define SAM_LUN_PROP (char *)fcp_sam_lun_prop #define CONF_WWN_PROP (char *)fcp_conf_wwn_prop #define OBP_BOOT_WWN (char *)fcp_obp_boot_wwn #define MANUAL_CFG_ONLY (char *)fcp_manual_config_only #define INIT_PORT_PROP (char *)fcp_init_port_prop #define TGT_PORT_PROP (char *)fcp_tgt_port_prop #define LUN_BLACKLIST_PROP (char *)fcp_lun_blacklist_prop /* * Short hand macros. */ #define LUN_PORT (plun->lun_tgt->tgt_port) #define LUN_TGT (plun->lun_tgt) /* * Driver private macros */ #define FCP_ATOB(x) (((x) >= '0' && (x) <= '9') ? ((x) - '0') : \ ((x) >= 'a' && (x) <= 'f') ? \ ((x) - 'a' + 10) : ((x) - 'A' + 10)) #define FCP_MAX(a, b) ((a) > (b) ? (a) : (b)) #define FCP_N_NDI_EVENTS \ (sizeof (fcp_ndi_event_defs) / sizeof (ndi_event_definition_t)) #define FCP_LINK_STATE_CHANGED(p, c) \ ((p)->port_link_cnt != (c)->ipkt_link_cnt) #define FCP_TGT_STATE_CHANGED(t, c) \ ((t)->tgt_change_cnt != (c)->ipkt_change_cnt) #define FCP_STATE_CHANGED(p, t, c) \ (FCP_TGT_STATE_CHANGED(t, c)) #define FCP_MUST_RETRY(fpkt) \ ((fpkt)->pkt_state == FC_PKT_LOCAL_BSY || \ (fpkt)->pkt_state == FC_PKT_LOCAL_RJT || \ (fpkt)->pkt_state == FC_PKT_TRAN_BSY || \ (fpkt)->pkt_state == FC_PKT_ELS_IN_PROGRESS || \ (fpkt)->pkt_state == FC_PKT_NPORT_BSY || \ (fpkt)->pkt_state == FC_PKT_FABRIC_BSY || \ (fpkt)->pkt_state == FC_PKT_PORT_OFFLINE || \ (fpkt)->pkt_reason == FC_REASON_OFFLINE) #define FCP_SENSE_REPORTLUN_CHANGED(es) \ ((es)->es_key == KEY_UNIT_ATTENTION && \ (es)->es_add_code == 0x3f && \ (es)->es_qual_code == 0x0e) #define FCP_SENSE_NO_LUN(es) \ ((es)->es_key == KEY_ILLEGAL_REQUEST && \ (es)->es_add_code == 0x25 && \ (es)->es_qual_code == 0x0) #define FCP_VERSION "1.187" #define FCP_NAME_VERSION "SunFC FCP v" FCP_VERSION #define FCP_NUM_ELEMENTS(array) \ (sizeof (array) / sizeof ((array)[0])) /* * Debugging, Error reporting, and tracing */ #define FCP_LOG_SIZE 1024 * 1024 #define FCP_LEVEL_1 0x00001 /* attach/detach PM CPR */ #define FCP_LEVEL_2 0x00002 /* failures/Invalid data */ #define FCP_LEVEL_3 0x00004 /* state change, discovery */ #define FCP_LEVEL_4 0x00008 /* ULP messages */ #define FCP_LEVEL_5 0x00010 /* ELS/SCSI cmds */ #define FCP_LEVEL_6 0x00020 /* Transport failures */ #define FCP_LEVEL_7 0x00040 #define FCP_LEVEL_8 0x00080 /* I/O tracing */ #define FCP_LEVEL_9 0x00100 /* I/O tracing */ /* * Log contents to system messages file */ #define FCP_MSG_LEVEL_1 (FCP_LEVEL_1 | FC_TRACE_LOG_MSG) #define FCP_MSG_LEVEL_2 (FCP_LEVEL_2 | FC_TRACE_LOG_MSG) #define FCP_MSG_LEVEL_3 (FCP_LEVEL_3 | FC_TRACE_LOG_MSG) #define FCP_MSG_LEVEL_4 (FCP_LEVEL_4 | FC_TRACE_LOG_MSG) #define FCP_MSG_LEVEL_5 (FCP_LEVEL_5 | FC_TRACE_LOG_MSG) #define FCP_MSG_LEVEL_6 (FCP_LEVEL_6 | FC_TRACE_LOG_MSG) #define FCP_MSG_LEVEL_7 (FCP_LEVEL_7 | FC_TRACE_LOG_MSG) #define FCP_MSG_LEVEL_8 (FCP_LEVEL_8 | FC_TRACE_LOG_MSG) #define FCP_MSG_LEVEL_9 (FCP_LEVEL_9 | FC_TRACE_LOG_MSG) /* * Log contents to trace buffer */ #define FCP_BUF_LEVEL_1 (FCP_LEVEL_1 | FC_TRACE_LOG_BUF) #define FCP_BUF_LEVEL_2 (FCP_LEVEL_2 | FC_TRACE_LOG_BUF) #define FCP_BUF_LEVEL_3 (FCP_LEVEL_3 | FC_TRACE_LOG_BUF) #define FCP_BUF_LEVEL_4 (FCP_LEVEL_4 | FC_TRACE_LOG_BUF) #define FCP_BUF_LEVEL_5 (FCP_LEVEL_5 | FC_TRACE_LOG_BUF) #define FCP_BUF_LEVEL_6 (FCP_LEVEL_6 | FC_TRACE_LOG_BUF) #define FCP_BUF_LEVEL_7 (FCP_LEVEL_7 | FC_TRACE_LOG_BUF) #define FCP_BUF_LEVEL_8 (FCP_LEVEL_8 | FC_TRACE_LOG_BUF) #define FCP_BUF_LEVEL_9 (FCP_LEVEL_9 | FC_TRACE_LOG_BUF) /* * Log contents to both system messages file and trace buffer */ #define FCP_MSG_BUF_LEVEL_1 (FCP_LEVEL_1 | FC_TRACE_LOG_BUF | \ FC_TRACE_LOG_MSG) #define FCP_MSG_BUF_LEVEL_2 (FCP_LEVEL_2 | FC_TRACE_LOG_BUF | \ FC_TRACE_LOG_MSG) #define FCP_MSG_BUF_LEVEL_3 (FCP_LEVEL_3 | FC_TRACE_LOG_BUF | \ FC_TRACE_LOG_MSG) #define FCP_MSG_BUF_LEVEL_4 (FCP_LEVEL_4 | FC_TRACE_LOG_BUF | \ FC_TRACE_LOG_MSG) #define FCP_MSG_BUF_LEVEL_5 (FCP_LEVEL_5 | FC_TRACE_LOG_BUF | \ FC_TRACE_LOG_MSG) #define FCP_MSG_BUF_LEVEL_6 (FCP_LEVEL_6 | FC_TRACE_LOG_BUF | \ FC_TRACE_LOG_MSG) #define FCP_MSG_BUF_LEVEL_7 (FCP_LEVEL_7 | FC_TRACE_LOG_BUF | \ FC_TRACE_LOG_MSG) #define FCP_MSG_BUF_LEVEL_8 (FCP_LEVEL_8 | FC_TRACE_LOG_BUF | \ FC_TRACE_LOG_MSG) #define FCP_MSG_BUF_LEVEL_9 (FCP_LEVEL_9 | FC_TRACE_LOG_BUF | \ FC_TRACE_LOG_MSG) #ifdef DEBUG #define FCP_DTRACE fc_trace_debug #else #define FCP_DTRACE #endif #define FCP_TRACE fc_trace_debug static struct cb_ops fcp_cb_ops = { fcp_open, /* open */ fcp_close, /* close */ nodev, /* strategy */ nodev, /* print */ nodev, /* dump */ nodev, /* read */ nodev, /* write */ fcp_ioctl, /* ioctl */ nodev, /* devmap */ nodev, /* mmap */ nodev, /* segmap */ nochpoll, /* chpoll */ ddi_prop_op, /* cb_prop_op */ 0, /* streamtab */ D_NEW | D_MP | D_HOTPLUG, /* cb_flag */ CB_REV, /* rev */ nodev, /* aread */ nodev /* awrite */ }; static struct dev_ops fcp_ops = { DEVO_REV, 0, ddi_getinfo_1to1, nulldev, /* identify */ nulldev, /* probe */ fcp_attach, /* attach and detach are mandatory */ fcp_detach, nodev, /* reset */ &fcp_cb_ops, /* cb_ops */ NULL, /* bus_ops */ NULL, /* power */ }; char *fcp_version = FCP_NAME_VERSION; static struct modldrv modldrv = { &mod_driverops, FCP_NAME_VERSION, &fcp_ops }; static struct modlinkage modlinkage = { MODREV_1, &modldrv, NULL }; static fc_ulp_modinfo_t fcp_modinfo = { &fcp_modinfo, /* ulp_handle */ FCTL_ULP_MODREV_4, /* ulp_rev */ FC4_SCSI_FCP, /* ulp_type */ "fcp", /* ulp_name */ FCP_STATEC_MASK, /* ulp_statec_mask */ fcp_port_attach, /* ulp_port_attach */ fcp_port_detach, /* ulp_port_detach */ fcp_port_ioctl, /* ulp_port_ioctl */ fcp_els_callback, /* ulp_els_callback */ fcp_data_callback, /* ulp_data_callback */ fcp_statec_callback /* ulp_statec_callback */ }; #ifdef DEBUG #define FCP_TRACE_DEFAULT (FC_TRACE_LOG_MASK | FCP_LEVEL_1 | \ FCP_LEVEL_2 | FCP_LEVEL_3 | \ FCP_LEVEL_4 | FCP_LEVEL_5 | \ FCP_LEVEL_6 | FCP_LEVEL_7) #else #define FCP_TRACE_DEFAULT (FC_TRACE_LOG_MASK | FCP_LEVEL_1 | \ FCP_LEVEL_2 | FCP_LEVEL_3 | \ FCP_LEVEL_4 | FCP_LEVEL_5 | \ FCP_LEVEL_6 | FCP_LEVEL_7) #endif /* FCP global variables */ int fcp_bus_config_debug = 0; static int fcp_log_size = FCP_LOG_SIZE; static int fcp_trace = FCP_TRACE_DEFAULT; static fc_trace_logq_t *fcp_logq = NULL; static struct fcp_black_list_entry *fcp_lun_blacklist = NULL; /* * The auto-configuration is set by default. The only way of disabling it is * through the property MANUAL_CFG_ONLY in the fcp.conf file. */ static int fcp_enable_auto_configuration = 1; static int fcp_max_bus_config_retries = 4; static int fcp_lun_ready_retry = 300; /* * The value assigned to the following variable has changed several times due * to a problem with the data underruns reporting of some firmware(s). The * current value of 50 gives a timeout value of 25 seconds for a max number * of 256 LUNs. */ static int fcp_max_target_retries = 50; /* * Watchdog variables * ------------------ * * fcp_watchdog_init * * Indicates if the watchdog timer is running or not. This is actually * a counter of the number of Fibre Channel ports that attached. When * the first port attaches the watchdog is started. When the last port * detaches the watchdog timer is stopped. * * fcp_watchdog_time * * This is the watchdog clock counter. It is incremented by * fcp_watchdog_time each time the watchdog timer expires. * * fcp_watchdog_timeout * * Increment value of the variable fcp_watchdog_time as well as the * the timeout value of the watchdog timer. The unit is 1 second. It * is strange that this is not a #define but a variable since the code * never changes this value. The reason why it can be said that the * unit is 1 second is because the number of ticks for the watchdog * timer is determined like this: * * fcp_watchdog_tick = fcp_watchdog_timeout * * drv_usectohz(1000000); * * The value 1000000 is hard coded in the code. * * fcp_watchdog_tick * * Watchdog timer value in ticks. */ static int fcp_watchdog_init = 0; static int fcp_watchdog_time = 0; static int fcp_watchdog_timeout = 1; static int fcp_watchdog_tick; /* * fcp_offline_delay is a global variable to enable customisation of * the timeout on link offlines or RSCNs. The default value is set * to match FCP_OFFLINE_DELAY (20sec), which is 2*RA_TOV_els as * specified in FCP4 Chapter 11 (see www.t10.org). * * The variable fcp_offline_delay is specified in SECONDS. * * If we made this a static var then the user would not be able to * change it. This variable is set in fcp_attach(). */ unsigned int fcp_offline_delay = FCP_OFFLINE_DELAY; static void *fcp_softstate = NULL; /* for soft state */ static uchar_t fcp_oflag = FCP_IDLE; /* open flag */ static kmutex_t fcp_global_mutex; static kmutex_t fcp_ioctl_mutex; static dev_info_t *fcp_global_dip = NULL; static timeout_id_t fcp_watchdog_id; const char *fcp_lun_prop = "lun"; const char *fcp_sam_lun_prop = "sam-lun"; const char *fcp_target_prop = "target"; /* * NOTE: consumers of "node-wwn" property include stmsboot in ON * consolidation. */ const char *fcp_node_wwn_prop = "node-wwn"; const char *fcp_port_wwn_prop = "port-wwn"; const char *fcp_conf_wwn_prop = "fc-port-wwn"; const char *fcp_obp_boot_wwn = "fc-boot-dev-portwwn"; const char *fcp_manual_config_only = "manual_configuration_only"; const char *fcp_init_port_prop = "initiator-port"; const char *fcp_tgt_port_prop = "target-port"; const char *fcp_lun_blacklist_prop = "pwwn-lun-blacklist"; static struct fcp_port *fcp_port_head = NULL; static ddi_eventcookie_t fcp_insert_eid; static ddi_eventcookie_t fcp_remove_eid; static ndi_event_definition_t fcp_ndi_event_defs[] = { { FCP_EVENT_TAG_INSERT, FCAL_INSERT_EVENT, EPL_KERNEL }, { FCP_EVENT_TAG_REMOVE, FCAL_REMOVE_EVENT, EPL_INTERRUPT } }; /* * List of valid commands for the scsi_ioctl call */ static uint8_t scsi_ioctl_list[] = { SCMD_INQUIRY, SCMD_REPORT_LUN, SCMD_READ_CAPACITY }; /* * this is used to dummy up a report lun response for cases * where the target doesn't support it */ static uchar_t fcp_dummy_lun[] = { 0x00, /* MSB length (length = no of luns * 8) */ 0x00, 0x00, 0x08, /* LSB length */ 0x00, /* MSB reserved */ 0x00, 0x00, 0x00, /* LSB reserved */ FCP_PD_ADDRESSING, 0x00, /* LUN is ZERO at the first level */ 0x00, 0x00, /* second level is zero */ 0x00, 0x00, /* third level is zero */ 0x00, 0x00 /* fourth level is zero */ }; static uchar_t fcp_alpa_to_switch[] = { 0x00, 0x7d, 0x7c, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x76, 0x00, 0x00, 0x75, 0x00, 0x74, 0x73, 0x72, 0x00, 0x00, 0x00, 0x71, 0x00, 0x70, 0x6f, 0x6e, 0x00, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x68, 0x00, 0x00, 0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x00, 0x00, 0x61, 0x60, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x5d, 0x5c, 0x5b, 0x00, 0x5a, 0x59, 0x58, 0x57, 0x56, 0x55, 0x00, 0x00, 0x54, 0x53, 0x52, 0x51, 0x50, 0x4f, 0x00, 0x00, 0x4e, 0x4d, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x4a, 0x49, 0x48, 0x00, 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x00, 0x00, 0x41, 0x40, 0x3f, 0x3e, 0x3d, 0x3c, 0x00, 0x00, 0x3b, 0x3a, 0x00, 0x39, 0x00, 0x00, 0x00, 0x38, 0x37, 0x36, 0x00, 0x35, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x30, 0x00, 0x00, 0x2f, 0x00, 0x2e, 0x2d, 0x2c, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x2a, 0x29, 0x28, 0x00, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x00, 0x00, 0x21, 0x20, 0x1f, 0x1e, 0x1d, 0x1c, 0x00, 0x00, 0x1b, 0x1a, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x17, 0x16, 0x15, 0x00, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, 0x00, 0x00, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x00, 0x00, 0x08, 0x07, 0x00, 0x06, 0x00, 0x00, 0x00, 0x05, 0x04, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; static caddr_t pid = "SESS01 "; #if !defined(lint) _NOTE(MUTEX_PROTECTS_DATA(fcp_global_mutex, fcp_port::fcp_next fcp_watchdog_id)) _NOTE(DATA_READABLE_WITHOUT_LOCK(fcp_watchdog_time)) _NOTE(SCHEME_PROTECTS_DATA("Unshared", fcp_insert_eid fcp_remove_eid fcp_watchdog_time)) _NOTE(SCHEME_PROTECTS_DATA("Unshared", fcp_cb_ops fcp_ops callb_cpr)) #endif /* lint */ /* * This table is used to determine whether or not it's safe to copy in * the target node name for a lun. Since all luns behind the same target * have the same wwnn, only tagets that do not support multiple luns are * eligible to be enumerated under mpxio if they aren't page83 compliant. */ char *fcp_symmetric_disk_table[] = { "SEAGATE ST", "IBM DDYFT", "SUNW SUNWGS", /* Daktari enclosure */ "SUN SENA", /* SES device */ "SUN SESS01" /* VICOM SVE box */ }; int fcp_symmetric_disk_table_size = sizeof (fcp_symmetric_disk_table)/sizeof (char *); /* * The _init(9e) return value should be that of mod_install(9f). Under * some circumstances, a failure may not be related mod_install(9f) and * one would then require a return value to indicate the failure. Looking * at mod_install(9f), it is expected to return 0 for success and non-zero * for failure. mod_install(9f) for device drivers, further goes down the * calling chain and ends up in ddi_installdrv(), whose return values are * DDI_SUCCESS and DDI_FAILURE - There are also other functions in the * calling chain of mod_install(9f) which return values like EINVAL and * in some even return -1. * * To work around the vagaries of the mod_install() calling chain, return * either 0 or ENODEV depending on the success or failure of mod_install() */ int _init(void) { int rval; /* * Allocate soft state and prepare to do ddi_soft_state_zalloc() * before registering with the transport first. */ if (ddi_soft_state_init(&fcp_softstate, sizeof (struct fcp_port), FCP_INIT_ITEMS) != 0) { return (EINVAL); } mutex_init(&fcp_global_mutex, NULL, MUTEX_DRIVER, NULL); mutex_init(&fcp_ioctl_mutex, NULL, MUTEX_DRIVER, NULL); if ((rval = fc_ulp_add(&fcp_modinfo)) != FC_SUCCESS) { cmn_err(CE_WARN, "fcp: fc_ulp_add failed"); mutex_destroy(&fcp_global_mutex); mutex_destroy(&fcp_ioctl_mutex); ddi_soft_state_fini(&fcp_softstate); return (ENODEV); } fcp_logq = fc_trace_alloc_logq(fcp_log_size); if ((rval = mod_install(&modlinkage)) != 0) { fc_trace_free_logq(fcp_logq); (void) fc_ulp_remove(&fcp_modinfo); mutex_destroy(&fcp_global_mutex); mutex_destroy(&fcp_ioctl_mutex); ddi_soft_state_fini(&fcp_softstate); rval = ENODEV; } return (rval); } /* * the system is done with us as a driver, so clean up */ int _fini(void) { int rval; /* * don't start cleaning up until we know that the module remove * has worked -- if this works, then we know that each instance * has successfully been DDI_DETACHed */ if ((rval = mod_remove(&modlinkage)) != 0) { return (rval); } (void) fc_ulp_remove(&fcp_modinfo); ddi_soft_state_fini(&fcp_softstate); mutex_destroy(&fcp_global_mutex); mutex_destroy(&fcp_ioctl_mutex); fc_trace_free_logq(fcp_logq); return (rval); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } /* * attach the module */ static int fcp_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) { int rval = DDI_SUCCESS; FCP_DTRACE(fcp_logq, "fcp", fcp_trace, FCP_BUF_LEVEL_8, 0, "fcp module attach: cmd=0x%x", cmd); if (cmd == DDI_ATTACH) { /* The FCP pseudo device is created here. */ mutex_enter(&fcp_global_mutex); fcp_global_dip = devi; mutex_exit(&fcp_global_mutex); if (ddi_create_minor_node(fcp_global_dip, "fcp", S_IFCHR, 0, DDI_PSEUDO, 0) == DDI_SUCCESS) { ddi_report_dev(fcp_global_dip); } else { cmn_err(CE_WARN, "FCP: Cannot create minor node"); mutex_enter(&fcp_global_mutex); fcp_global_dip = NULL; mutex_exit(&fcp_global_mutex); rval = DDI_FAILURE; } /* * We check the fcp_offline_delay property at this * point. This variable is global for the driver, * not specific to an instance. * * We do not recommend setting the value to less * than 10 seconds (RA_TOV_els), or greater than * 60 seconds. */ fcp_offline_delay = ddi_prop_get_int(DDI_DEV_T_ANY, devi, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "fcp_offline_delay", FCP_OFFLINE_DELAY); if ((fcp_offline_delay < 10) || (fcp_offline_delay > 60)) { cmn_err(CE_WARN, "Setting fcp_offline_delay " "to %d second(s). This is outside the " "recommended range of 10..60 seconds.", fcp_offline_delay); } } return (rval); } /*ARGSUSED*/ static int fcp_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) { int res = DDI_SUCCESS; FCP_DTRACE(fcp_logq, "fcp", fcp_trace, FCP_BUF_LEVEL_8, 0, "module detach: cmd=0x%x", cmd); if (cmd == DDI_DETACH) { /* * Check if there are active ports/threads. If there * are any, we will fail, else we will succeed (there * should not be much to clean up) */ mutex_enter(&fcp_global_mutex); FCP_DTRACE(fcp_logq, "fcp", fcp_trace, FCP_BUF_LEVEL_8, 0, "port_head=%p", (void *) fcp_port_head); if (fcp_port_head == NULL) { ddi_remove_minor_node(fcp_global_dip, NULL); fcp_global_dip = NULL; mutex_exit(&fcp_global_mutex); } else { mutex_exit(&fcp_global_mutex); res = DDI_FAILURE; } } FCP_DTRACE(fcp_logq, "fcp", fcp_trace, FCP_BUF_LEVEL_8, 0, "module detach returning %d", res); return (res); } /* ARGSUSED */ static int fcp_open(dev_t *devp, int flag, int otype, cred_t *credp) { if (otype != OTYP_CHR) { return (EINVAL); } /* * Allow only root to talk; */ if (drv_priv(credp)) { return (EPERM); } mutex_enter(&fcp_global_mutex); if (fcp_oflag & FCP_EXCL) { mutex_exit(&fcp_global_mutex); return (EBUSY); } if (flag & FEXCL) { if (fcp_oflag & FCP_OPEN) { mutex_exit(&fcp_global_mutex); return (EBUSY); } fcp_oflag |= FCP_EXCL; } fcp_oflag |= FCP_OPEN; mutex_exit(&fcp_global_mutex); return (0); } /* ARGSUSED */ static int fcp_close(dev_t dev, int flag, int otype, cred_t *credp) { if (otype != OTYP_CHR) { return (EINVAL); } mutex_enter(&fcp_global_mutex); if (!(fcp_oflag & FCP_OPEN)) { mutex_exit(&fcp_global_mutex); return (ENODEV); } fcp_oflag = FCP_IDLE; mutex_exit(&fcp_global_mutex); return (0); } /* * fcp_ioctl * Entry point for the FCP ioctls * * Input: * See ioctl(9E) * * Output: * See ioctl(9E) * * Returns: * See ioctl(9E) * * Context: * Kernel context. */ /* ARGSUSED */ static int fcp_ioctl(dev_t dev, int cmd, intptr_t data, int mode, cred_t *credp, int *rval) { int ret = 0; mutex_enter(&fcp_global_mutex); if (!(fcp_oflag & FCP_OPEN)) { mutex_exit(&fcp_global_mutex); return (ENXIO); } mutex_exit(&fcp_global_mutex); switch (cmd) { case FCP_TGT_INQUIRY: case FCP_TGT_CREATE: case FCP_TGT_DELETE: ret = fcp_setup_device_data_ioctl(cmd, (struct fcp_ioctl *)data, mode, rval); break; case FCP_TGT_SEND_SCSI: mutex_enter(&fcp_ioctl_mutex); ret = fcp_setup_scsi_ioctl( (struct fcp_scsi_cmd *)data, mode, rval); mutex_exit(&fcp_ioctl_mutex); break; case FCP_STATE_COUNT: ret = fcp_get_statec_count((struct fcp_ioctl *)data, mode, rval); break; case FCP_GET_TARGET_MAPPINGS: ret = fcp_get_target_mappings((struct fcp_ioctl *)data, mode, rval); break; default: fcp_log(CE_WARN, NULL, "!Invalid ioctl opcode = 0x%x", cmd); ret = EINVAL; } return (ret); } /* * fcp_setup_device_data_ioctl * Setup handler for the "device data" style of * ioctl for FCP. See "fcp_util.h" for data structure * definition. * * Input: * cmd = FCP ioctl command * data = ioctl data * mode = See ioctl(9E) * * Output: * data = ioctl data * rval = return value - see ioctl(9E) * * Returns: * See ioctl(9E) * * Context: * Kernel context. */ /* ARGSUSED */ static int fcp_setup_device_data_ioctl(int cmd, struct fcp_ioctl *data, int mode, int *rval) { struct fcp_port *pptr; struct device_data *dev_data; uint32_t link_cnt; la_wwn_t *wwn_ptr = NULL; struct fcp_tgt *ptgt = NULL; struct fcp_lun *plun = NULL; int i, error; struct fcp_ioctl fioctl; #ifdef _MULTI_DATAMODEL switch (ddi_model_convert_from(mode & FMODELS)) { case DDI_MODEL_ILP32: { struct fcp32_ioctl f32_ioctl; if (ddi_copyin((void *)data, (void *)&f32_ioctl, sizeof (struct fcp32_ioctl), mode)) { return (EFAULT); } fioctl.fp_minor = f32_ioctl.fp_minor; fioctl.listlen = f32_ioctl.listlen; fioctl.list = (caddr_t)(long)f32_ioctl.list; break; } case DDI_MODEL_NONE: if (ddi_copyin((void *)data, (void *)&fioctl, sizeof (struct fcp_ioctl), mode)) { return (EFAULT); } break; } #else /* _MULTI_DATAMODEL */ if (ddi_copyin((void *)data, (void *)&fioctl, sizeof (struct fcp_ioctl), mode)) { return (EFAULT); } #endif /* _MULTI_DATAMODEL */ /* * Right now we can assume that the minor number matches with * this instance of fp. If this changes we will need to * revisit this logic. */ mutex_enter(&fcp_global_mutex); pptr = fcp_port_head; while (pptr) { if (pptr->port_instance == (uint32_t)fioctl.fp_minor) { break; } else { pptr = pptr->port_next; } } mutex_exit(&fcp_global_mutex); if (pptr == NULL) { return (ENXIO); } mutex_enter(&pptr->port_mutex); if ((dev_data = kmem_zalloc((sizeof (struct device_data)) * fioctl.listlen, KM_NOSLEEP)) == NULL) { mutex_exit(&pptr->port_mutex); return (ENOMEM); } if (ddi_copyin(fioctl.list, dev_data, (sizeof (struct device_data)) * fioctl.listlen, mode)) { kmem_free(dev_data, sizeof (*dev_data) * fioctl.listlen); mutex_exit(&pptr->port_mutex); return (EFAULT); } link_cnt = pptr->port_link_cnt; if (cmd == FCP_TGT_INQUIRY) { wwn_ptr = (la_wwn_t *)&(dev_data[0].dev_pwwn); if (bcmp(wwn_ptr->raw_wwn, pptr->port_pwwn.raw_wwn, sizeof (wwn_ptr->raw_wwn)) == 0) { /* This ioctl is requesting INQ info of local HBA */ mutex_exit(&pptr->port_mutex); dev_data[0].dev0_type = DTYPE_UNKNOWN; dev_data[0].dev_status = 0; if (ddi_copyout(dev_data, fioctl.list, (sizeof (struct device_data)) * fioctl.listlen, mode)) { kmem_free(dev_data, sizeof (*dev_data) * fioctl.listlen); return (EFAULT); } kmem_free(dev_data, sizeof (*dev_data) * fioctl.listlen); #ifdef _MULTI_DATAMODEL switch (ddi_model_convert_from(mode & FMODELS)) { case DDI_MODEL_ILP32: { struct fcp32_ioctl f32_ioctl; f32_ioctl.fp_minor = fioctl.fp_minor; f32_ioctl.listlen = fioctl.listlen; f32_ioctl.list = (caddr32_t)(long)fioctl.list; if (ddi_copyout((void *)&f32_ioctl, (void *)data, sizeof (struct fcp32_ioctl), mode)) { return (EFAULT); } break; } case DDI_MODEL_NONE: if (ddi_copyout((void *)&fioctl, (void *)data, sizeof (struct fcp_ioctl), mode)) { return (EFAULT); } break; } #else /* _MULTI_DATAMODEL */ if (ddi_copyout((void *)&fioctl, (void *)data, sizeof (struct fcp_ioctl), mode)) { return (EFAULT); } #endif /* _MULTI_DATAMODEL */ return (0); } } if (pptr->port_state & (FCP_STATE_INIT | FCP_STATE_OFFLINE)) { kmem_free(dev_data, sizeof (*dev_data) * fioctl.listlen); mutex_exit(&pptr->port_mutex); return (ENXIO); } for (i = 0; (i < fioctl.listlen) && (link_cnt == pptr->port_link_cnt); i++) { wwn_ptr = (la_wwn_t *)&(dev_data[i].dev_pwwn); dev_data[i].dev0_type = DTYPE_UNKNOWN; dev_data[i].dev_status = ENXIO; if ((ptgt = fcp_lookup_target(pptr, (uchar_t *)wwn_ptr)) == NULL) { mutex_exit(&pptr->port_mutex); if (fc_ulp_get_remote_port(pptr->port_fp_handle, wwn_ptr, &error, 0) == NULL) { dev_data[i].dev_status = ENODEV; mutex_enter(&pptr->port_mutex); continue; } else { dev_data[i].dev_status = EAGAIN; mutex_enter(&pptr->port_mutex); continue; } } else { mutex_enter(&ptgt->tgt_mutex); if (ptgt->tgt_state & (FCP_TGT_MARK | FCP_TGT_BUSY)) { dev_data[i].dev_status = EAGAIN; mutex_exit(&ptgt->tgt_mutex); continue; } if (ptgt->tgt_state & FCP_TGT_OFFLINE) { if (ptgt->tgt_icap && !ptgt->tgt_tcap) { dev_data[i].dev_status = ENOTSUP; } else { dev_data[i].dev_status = ENXIO; } mutex_exit(&ptgt->tgt_mutex); continue; } switch (cmd) { case FCP_TGT_INQUIRY: /* * The reason we give device type of * lun 0 only even though in some * cases(like maxstrat) lun 0 device * type may be 0x3f(invalid) is that * for bridge boxes target will appear * as luns and the first lun could be * a device that utility may not care * about (like a tape device). */ dev_data[i].dev_lun_cnt = ptgt->tgt_lun_cnt; dev_data[i].dev_status = 0; mutex_exit(&ptgt->tgt_mutex); if ((plun = fcp_get_lun(ptgt, 0)) == NULL) { dev_data[i].dev0_type = DTYPE_UNKNOWN; } else { dev_data[i].dev0_type = plun->lun_type; } mutex_enter(&ptgt->tgt_mutex); break; case FCP_TGT_CREATE: mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); /* * serialize state change call backs. * only one call back will be handled * at a time. */ mutex_enter(&fcp_global_mutex); if (fcp_oflag & FCP_BUSY) { mutex_exit(&fcp_global_mutex); if (dev_data) { kmem_free(dev_data, sizeof (*dev_data) * fioctl.listlen); } return (EBUSY); } fcp_oflag |= FCP_BUSY; mutex_exit(&fcp_global_mutex); dev_data[i].dev_status = fcp_create_on_demand(pptr, wwn_ptr->raw_wwn); if (dev_data[i].dev_status != 0) { char buf[25]; for (i = 0; i < FC_WWN_SIZE; i++) { (void) sprintf(&buf[i << 1], "%02x", wwn_ptr->raw_wwn[i]); } fcp_log(CE_WARN, pptr->port_dip, "!Failed to create nodes for" " pwwn=%s; error=%x", buf, dev_data[i].dev_status); } /* allow state change call backs again */ mutex_enter(&fcp_global_mutex); fcp_oflag &= ~FCP_BUSY; mutex_exit(&fcp_global_mutex); mutex_enter(&pptr->port_mutex); mutex_enter(&ptgt->tgt_mutex); break; case FCP_TGT_DELETE: break; default: fcp_log(CE_WARN, pptr->port_dip, "!Invalid device data ioctl " "opcode = 0x%x", cmd); } mutex_exit(&ptgt->tgt_mutex); } } mutex_exit(&pptr->port_mutex); if (ddi_copyout(dev_data, fioctl.list, (sizeof (struct device_data)) * fioctl.listlen, mode)) { kmem_free(dev_data, sizeof (*dev_data) * fioctl.listlen); return (EFAULT); } kmem_free(dev_data, sizeof (*dev_data) * fioctl.listlen); #ifdef _MULTI_DATAMODEL switch (ddi_model_convert_from(mode & FMODELS)) { case DDI_MODEL_ILP32: { struct fcp32_ioctl f32_ioctl; f32_ioctl.fp_minor = fioctl.fp_minor; f32_ioctl.listlen = fioctl.listlen; f32_ioctl.list = (caddr32_t)(long)fioctl.list; if (ddi_copyout((void *)&f32_ioctl, (void *)data, sizeof (struct fcp32_ioctl), mode)) { return (EFAULT); } break; } case DDI_MODEL_NONE: if (ddi_copyout((void *)&fioctl, (void *)data, sizeof (struct fcp_ioctl), mode)) { return (EFAULT); } break; } #else /* _MULTI_DATAMODEL */ if (ddi_copyout((void *)&fioctl, (void *)data, sizeof (struct fcp_ioctl), mode)) { return (EFAULT); } #endif /* _MULTI_DATAMODEL */ return (0); } /* * Fetch the target mappings (path, etc.) for all LUNs * on this port. */ /* ARGSUSED */ static int fcp_get_target_mappings(struct fcp_ioctl *data, int mode, int *rval) { struct fcp_port *pptr; fc_hba_target_mappings_t *mappings; fc_hba_mapping_entry_t *map; struct fcp_tgt *ptgt = NULL; struct fcp_lun *plun = NULL; int i, mapIndex, mappingSize; int listlen; struct fcp_ioctl fioctl; char *path; fcp_ent_addr_t sam_lun_addr; #ifdef _MULTI_DATAMODEL switch (ddi_model_convert_from(mode & FMODELS)) { case DDI_MODEL_ILP32: { struct fcp32_ioctl f32_ioctl; if (ddi_copyin((void *)data, (void *)&f32_ioctl, sizeof (struct fcp32_ioctl), mode)) { return (EFAULT); } fioctl.fp_minor = f32_ioctl.fp_minor; fioctl.listlen = f32_ioctl.listlen; fioctl.list = (caddr_t)(long)f32_ioctl.list; break; } case DDI_MODEL_NONE: if (ddi_copyin((void *)data, (void *)&fioctl, sizeof (struct fcp_ioctl), mode)) { return (EFAULT); } break; } #else /* _MULTI_DATAMODEL */ if (ddi_copyin((void *)data, (void *)&fioctl, sizeof (struct fcp_ioctl), mode)) { return (EFAULT); } #endif /* _MULTI_DATAMODEL */ /* * Right now we can assume that the minor number matches with * this instance of fp. If this changes we will need to * revisit this logic. */ mutex_enter(&fcp_global_mutex); pptr = fcp_port_head; while (pptr) { if (pptr->port_instance == (uint32_t)fioctl.fp_minor) { break; } else { pptr = pptr->port_next; } } mutex_exit(&fcp_global_mutex); if (pptr == NULL) { cmn_err(CE_NOTE, "target mappings: unknown instance number: %d", fioctl.fp_minor); return (ENXIO); } /* We use listlen to show the total buffer size */ mappingSize = fioctl.listlen; /* Now calculate how many mapping entries will fit */ listlen = fioctl.listlen + sizeof (fc_hba_mapping_entry_t) - sizeof (fc_hba_target_mappings_t); if (listlen <= 0) { cmn_err(CE_NOTE, "target mappings: Insufficient buffer"); return (ENXIO); } listlen = listlen / sizeof (fc_hba_mapping_entry_t); if ((mappings = kmem_zalloc(mappingSize, KM_SLEEP)) == NULL) { return (ENOMEM); } mappings->version = FC_HBA_TARGET_MAPPINGS_VERSION; /* Now get to work */ mapIndex = 0; mutex_enter(&pptr->port_mutex); /* Loop through all targets on this port */ for (i = 0; i < FCP_NUM_HASH; i++) { for (ptgt = pptr->port_tgt_hash_table[i]; ptgt != NULL; ptgt = ptgt->tgt_next) { /* Loop through all LUNs on this target */ for (plun = ptgt->tgt_lun; plun != NULL; plun = plun->lun_next) { if (plun->lun_state & FCP_LUN_OFFLINE) { continue; } path = fcp_get_lun_path(plun); if (path == NULL) { continue; } if (mapIndex >= listlen) { mapIndex ++; kmem_free(path, MAXPATHLEN); continue; } map = &mappings->entries[mapIndex++]; bcopy(path, map->targetDriver, sizeof (map->targetDriver)); map->d_id = ptgt->tgt_d_id; map->busNumber = 0; map->targetNumber = ptgt->tgt_d_id; map->osLUN = plun->lun_num; /* * We had swapped lun when we stored it in * lun_addr. We need to swap it back before * returning it to user land */ sam_lun_addr.ent_addr_0 = BE_16(plun->lun_addr.ent_addr_0); sam_lun_addr.ent_addr_1 = BE_16(plun->lun_addr.ent_addr_1); sam_lun_addr.ent_addr_2 = BE_16(plun->lun_addr.ent_addr_2); sam_lun_addr.ent_addr_3 = BE_16(plun->lun_addr.ent_addr_3); bcopy(&sam_lun_addr, &map->samLUN, FCP_LUN_SIZE); bcopy(ptgt->tgt_node_wwn.raw_wwn, map->NodeWWN.raw_wwn, sizeof (la_wwn_t)); bcopy(ptgt->tgt_port_wwn.raw_wwn, map->PortWWN.raw_wwn, sizeof (la_wwn_t)); if (plun->lun_guid) { /* convert ascii wwn to bytes */ fcp_ascii_to_wwn(plun->lun_guid, map->guid, sizeof (map->guid)); if ((sizeof (map->guid)) < plun->lun_guid_size / 2) { cmn_err(CE_WARN, "fcp_get_target_mappings:" "guid copy space " "insufficient." "Copy Truncation - " "available %d; need %d", (int)sizeof (map->guid), (int) plun->lun_guid_size / 2); } } kmem_free(path, MAXPATHLEN); } } } mutex_exit(&pptr->port_mutex); mappings->numLuns = mapIndex; if (ddi_copyout(mappings, fioctl.list, mappingSize, mode)) { kmem_free(mappings, mappingSize); return (EFAULT); } kmem_free(mappings, mappingSize); #ifdef _MULTI_DATAMODEL switch (ddi_model_convert_from(mode & FMODELS)) { case DDI_MODEL_ILP32: { struct fcp32_ioctl f32_ioctl; f32_ioctl.fp_minor = fioctl.fp_minor; f32_ioctl.listlen = fioctl.listlen; f32_ioctl.list = (caddr32_t)(long)fioctl.list; if (ddi_copyout((void *)&f32_ioctl, (void *)data, sizeof (struct fcp32_ioctl), mode)) { return (EFAULT); } break; } case DDI_MODEL_NONE: if (ddi_copyout((void *)&fioctl, (void *)data, sizeof (struct fcp_ioctl), mode)) { return (EFAULT); } break; } #else /* _MULTI_DATAMODEL */ if (ddi_copyout((void *)&fioctl, (void *)data, sizeof (struct fcp_ioctl), mode)) { return (EFAULT); } #endif /* _MULTI_DATAMODEL */ return (0); } /* * fcp_setup_scsi_ioctl * Setup handler for the "scsi passthru" style of * ioctl for FCP. See "fcp_util.h" for data structure * definition. * * Input: * u_fscsi = ioctl data (user address space) * mode = See ioctl(9E) * * Output: * u_fscsi = ioctl data (user address space) * rval = return value - see ioctl(9E) * * Returns: * 0 = OK * EAGAIN = See errno.h * EBUSY = See errno.h * EFAULT = See errno.h * EINTR = See errno.h * EINVAL = See errno.h * EIO = See errno.h * ENOMEM = See errno.h * ENXIO = See errno.h * * Context: * Kernel context. */ /* ARGSUSED */ static int fcp_setup_scsi_ioctl(struct fcp_scsi_cmd *u_fscsi, int mode, int *rval) { int ret = 0; int temp_ret; caddr_t k_cdbbufaddr = NULL; caddr_t k_bufaddr = NULL; caddr_t k_rqbufaddr = NULL; caddr_t u_cdbbufaddr; caddr_t u_bufaddr; caddr_t u_rqbufaddr; struct fcp_scsi_cmd k_fscsi; /* * Get fcp_scsi_cmd array element from user address space */ if ((ret = fcp_copyin_scsi_cmd((caddr_t)u_fscsi, &k_fscsi, mode)) != 0) { return (ret); } /* * Even though kmem_alloc() checks the validity of the * buffer length, this check is needed when the * kmem_flags set and the zero buffer length is passed. */ if ((k_fscsi.scsi_cdblen <= 0) || (k_fscsi.scsi_buflen <= 0) || (k_fscsi.scsi_buflen > FCP_MAX_RESPONSE_LEN) || (k_fscsi.scsi_rqlen <= 0) || (k_fscsi.scsi_rqlen > FCP_MAX_SENSE_LEN)) { return (EINVAL); } /* * Allocate data for fcp_scsi_cmd pointer fields */ if (ret == 0) { k_cdbbufaddr = kmem_alloc(k_fscsi.scsi_cdblen, KM_NOSLEEP); k_bufaddr = kmem_alloc(k_fscsi.scsi_buflen, KM_NOSLEEP); k_rqbufaddr = kmem_alloc(k_fscsi.scsi_rqlen, KM_NOSLEEP); if (k_cdbbufaddr == NULL || k_bufaddr == NULL || k_rqbufaddr == NULL) { ret = ENOMEM; } } /* * Get fcp_scsi_cmd pointer fields from user * address space */ if (ret == 0) { u_cdbbufaddr = k_fscsi.scsi_cdbbufaddr; u_bufaddr = k_fscsi.scsi_bufaddr; u_rqbufaddr = k_fscsi.scsi_rqbufaddr; if (ddi_copyin(u_cdbbufaddr, k_cdbbufaddr, k_fscsi.scsi_cdblen, mode)) { ret = EFAULT; } else if (ddi_copyin(u_bufaddr, k_bufaddr, k_fscsi.scsi_buflen, mode)) { ret = EFAULT; } else if (ddi_copyin(u_rqbufaddr, k_rqbufaddr, k_fscsi.scsi_rqlen, mode)) { ret = EFAULT; } } /* * Send scsi command (blocking) */ if (ret == 0) { /* * Prior to sending the scsi command, the * fcp_scsi_cmd data structure must contain kernel, * not user, addresses. */ k_fscsi.scsi_cdbbufaddr = k_cdbbufaddr; k_fscsi.scsi_bufaddr = k_bufaddr; k_fscsi.scsi_rqbufaddr = k_rqbufaddr; ret = fcp_send_scsi_ioctl(&k_fscsi); /* * After sending the scsi command, the * fcp_scsi_cmd data structure must contain user, * not kernel, addresses. */ k_fscsi.scsi_cdbbufaddr = u_cdbbufaddr; k_fscsi.scsi_bufaddr = u_bufaddr; k_fscsi.scsi_rqbufaddr = u_rqbufaddr; } /* * Put fcp_scsi_cmd pointer fields to user address space */ if (ret == 0) { if (ddi_copyout(k_cdbbufaddr, u_cdbbufaddr, k_fscsi.scsi_cdblen, mode)) { ret = EFAULT; } else if (ddi_copyout(k_bufaddr, u_bufaddr, k_fscsi.scsi_buflen, mode)) { ret = EFAULT; } else if (ddi_copyout(k_rqbufaddr, u_rqbufaddr, k_fscsi.scsi_rqlen, mode)) { ret = EFAULT; } } /* * Free data for fcp_scsi_cmd pointer fields */ if (k_cdbbufaddr != NULL) { kmem_free(k_cdbbufaddr, k_fscsi.scsi_cdblen); } if (k_bufaddr != NULL) { kmem_free(k_bufaddr, k_fscsi.scsi_buflen); } if (k_rqbufaddr != NULL) { kmem_free(k_rqbufaddr, k_fscsi.scsi_rqlen); } /* * Put fcp_scsi_cmd array element to user address space */ temp_ret = fcp_copyout_scsi_cmd(&k_fscsi, (caddr_t)u_fscsi, mode); if (temp_ret != 0) { ret = temp_ret; } /* * Return status */ return (ret); } /* * fcp_copyin_scsi_cmd * Copy in fcp_scsi_cmd data structure from user address space. * The data may be in 32 bit or 64 bit modes. * * Input: * base_addr = from address (user address space) * mode = See ioctl(9E) and ddi_copyin(9F) * * Output: * fscsi = to address (kernel address space) * * Returns: * 0 = OK * EFAULT = Error * * Context: * Kernel context. */ static int fcp_copyin_scsi_cmd(caddr_t base_addr, struct fcp_scsi_cmd *fscsi, int mode) { #ifdef _MULTI_DATAMODEL struct fcp32_scsi_cmd f32scsi; switch (ddi_model_convert_from(mode & FMODELS)) { case DDI_MODEL_ILP32: /* * Copy data from user address space */ if (ddi_copyin((void *)base_addr, &f32scsi, sizeof (struct fcp32_scsi_cmd), mode)) { return (EFAULT); } /* * Convert from 32 bit to 64 bit */ FCP32_SCSI_CMD_TO_FCP_SCSI_CMD(&f32scsi, fscsi); break; case DDI_MODEL_NONE: /* * Copy data from user address space */ if (ddi_copyin((void *)base_addr, fscsi, sizeof (struct fcp_scsi_cmd), mode)) { return (EFAULT); } break; } #else /* _MULTI_DATAMODEL */ /* * Copy data from user address space */ if (ddi_copyin((void *)base_addr, fscsi, sizeof (struct fcp_scsi_cmd), mode)) { return (EFAULT); } #endif /* _MULTI_DATAMODEL */ return (0); } /* * fcp_copyout_scsi_cmd * Copy out fcp_scsi_cmd data structure to user address space. * The data may be in 32 bit or 64 bit modes. * * Input: * fscsi = to address (kernel address space) * mode = See ioctl(9E) and ddi_copyin(9F) * * Output: * base_addr = from address (user address space) * * Returns: * 0 = OK * EFAULT = Error * * Context: * Kernel context. */ static int fcp_copyout_scsi_cmd(struct fcp_scsi_cmd *fscsi, caddr_t base_addr, int mode) { #ifdef _MULTI_DATAMODEL struct fcp32_scsi_cmd f32scsi; switch (ddi_model_convert_from(mode & FMODELS)) { case DDI_MODEL_ILP32: /* * Convert from 64 bit to 32 bit */ FCP_SCSI_CMD_TO_FCP32_SCSI_CMD(fscsi, &f32scsi); /* * Copy data to user address space */ if (ddi_copyout(&f32scsi, (void *)base_addr, sizeof (struct fcp32_scsi_cmd), mode)) { return (EFAULT); } break; case DDI_MODEL_NONE: /* * Copy data to user address space */ if (ddi_copyout(fscsi, (void *)base_addr, sizeof (struct fcp_scsi_cmd), mode)) { return (EFAULT); } break; } #else /* _MULTI_DATAMODEL */ /* * Copy data to user address space */ if (ddi_copyout(fscsi, (void *)base_addr, sizeof (struct fcp_scsi_cmd), mode)) { return (EFAULT); } #endif /* _MULTI_DATAMODEL */ return (0); } /* * fcp_send_scsi_ioctl * Sends the SCSI command in blocking mode. * * Input: * fscsi = SCSI command data structure * * Output: * fscsi = SCSI command data structure * * Returns: * 0 = OK * EAGAIN = See errno.h * EBUSY = See errno.h * EINTR = See errno.h * EINVAL = See errno.h * EIO = See errno.h * ENOMEM = See errno.h * ENXIO = See errno.h * * Context: * Kernel context. */ static int fcp_send_scsi_ioctl(struct fcp_scsi_cmd *fscsi) { struct fcp_lun *plun = NULL; struct fcp_port *pptr = NULL; struct fcp_tgt *ptgt = NULL; fc_packet_t *fpkt = NULL; struct fcp_ipkt *icmd = NULL; int target_created = FALSE; fc_frame_hdr_t *hp; struct fcp_cmd fcp_cmd; struct fcp_cmd *fcmd; union scsi_cdb *scsi_cdb; la_wwn_t *wwn_ptr; int nodma; struct fcp_rsp *rsp; struct fcp_rsp_info *rsp_info; caddr_t rsp_sense; int buf_len; int info_len; int sense_len; struct scsi_extended_sense *sense_to = NULL; timeout_id_t tid; uint8_t reconfig_lun = FALSE; uint8_t reconfig_pending = FALSE; uint8_t scsi_cmd; int rsp_len; int cmd_index; int fc_status; int pkt_state; int pkt_action; int pkt_reason; int ret, xport_retval = ~FC_SUCCESS; int lcount; int tcount; int reconfig_status; int port_busy = FALSE; uchar_t *lun_string; /* * Check valid SCSI command */ scsi_cmd = ((uint8_t *)fscsi->scsi_cdbbufaddr)[0]; ret = EINVAL; for (cmd_index = 0; cmd_index < FCP_NUM_ELEMENTS(scsi_ioctl_list) && ret != 0; cmd_index++) { /* * First byte of CDB is the SCSI command */ if (scsi_ioctl_list[cmd_index] == scsi_cmd) { ret = 0; } } /* * Check inputs */ if (fscsi->scsi_flags != FCP_SCSI_READ) { ret = EINVAL; } else if (fscsi->scsi_cdblen > FCP_CDB_SIZE) { /* no larger than */ ret = EINVAL; } /* * Find FC port */ if (ret == 0) { /* * Acquire global mutex */ mutex_enter(&fcp_global_mutex); pptr = fcp_port_head; while (pptr) { if (pptr->port_instance == (uint32_t)fscsi->scsi_fc_port_num) { break; } else { pptr = pptr->port_next; } } if (pptr == NULL) { ret = ENXIO; } else { /* * fc_ulp_busy_port can raise power * so, we must not hold any mutexes involved in PM */ mutex_exit(&fcp_global_mutex); ret = fc_ulp_busy_port(pptr->port_fp_handle); } if (ret == 0) { /* remember port is busy, so we will release later */ port_busy = TRUE; /* * If there is a reconfiguration in progress, wait * for it to complete. */ fcp_reconfig_wait(pptr); /* reacquire mutexes in order */ mutex_enter(&fcp_global_mutex); mutex_enter(&pptr->port_mutex); /* * Will port accept DMA? */ nodma = (pptr->port_fcp_dma == FC_NO_DVMA_SPACE) ? 1 : 0; /* * If init or offline, device not known * * If we are discovering (onlining), we can * NOT obviously provide reliable data about * devices until it is complete */ if (pptr->port_state & (FCP_STATE_INIT | FCP_STATE_OFFLINE)) { ret = ENXIO; } else if (pptr->port_state & FCP_STATE_ONLINING) { ret = EBUSY; } else { /* * Find target from pwwn * * The wwn must be put into a local * variable to ensure alignment. */ wwn_ptr = (la_wwn_t *)&(fscsi->scsi_fc_pwwn); ptgt = fcp_lookup_target(pptr, (uchar_t *)wwn_ptr); /* * If target not found, */ if (ptgt == NULL) { /* * Note: Still have global & * port mutexes */ mutex_exit(&pptr->port_mutex); ptgt = fcp_port_create_tgt(pptr, wwn_ptr, &ret, &fc_status, &pkt_state, &pkt_action, &pkt_reason); mutex_enter(&pptr->port_mutex); fscsi->scsi_fc_status = fc_status; fscsi->scsi_pkt_state = (uchar_t)pkt_state; fscsi->scsi_pkt_reason = pkt_reason; fscsi->scsi_pkt_action = (uchar_t)pkt_action; if (ptgt != NULL) { target_created = TRUE; } else if (ret == 0) { ret = ENOMEM; } } if (ret == 0) { /* * Acquire target */ mutex_enter(&ptgt->tgt_mutex); /* * If target is mark or busy, * then target can not be used */ if (ptgt->tgt_state & (FCP_TGT_MARK | FCP_TGT_BUSY)) { ret = EBUSY; } else { /* * Mark target as busy */ ptgt->tgt_state |= FCP_TGT_BUSY; } /* * Release target */ lcount = pptr->port_link_cnt; tcount = ptgt->tgt_change_cnt; mutex_exit(&ptgt->tgt_mutex); } } /* * Release port */ mutex_exit(&pptr->port_mutex); } /* * Release global mutex */ mutex_exit(&fcp_global_mutex); } if (ret == 0) { uint64_t belun = BE_64(fscsi->scsi_lun); /* * If it's a target device, find lun from pwwn * The wwn must be put into a local * variable to ensure alignment. */ mutex_enter(&pptr->port_mutex); wwn_ptr = (la_wwn_t *)&(fscsi->scsi_fc_pwwn); if (!ptgt->tgt_tcap && ptgt->tgt_icap) { /* this is not a target */ fscsi->scsi_fc_status = FC_DEVICE_NOT_TGT; ret = ENXIO; } else if ((belun << 16) != 0) { /* * Since fcp only support PD and LU addressing method * so far, the last 6 bytes of a valid LUN are expected * to be filled with 00h. */ fscsi->scsi_fc_status = FC_INVALID_LUN; cmn_err(CE_WARN, "fcp: Unsupported LUN addressing" " method 0x%02x with LUN number 0x%016" PRIx64, (uint8_t)(belun >> 62), belun); ret = ENXIO; } else if ((plun = fcp_lookup_lun(pptr, (uchar_t *)wwn_ptr, (uint16_t)((belun >> 48) & 0x3fff))) == NULL) { /* * This is a SCSI target, but no LUN at this * address. * * In the future, we may want to send this to * the target, and let it respond * appropriately */ ret = ENXIO; } mutex_exit(&pptr->port_mutex); } /* * Finished grabbing external resources * Allocate internal packet (icmd) */ if (ret == 0) { /* * Calc rsp len assuming rsp info included */ rsp_len = sizeof (struct fcp_rsp) + sizeof (struct fcp_rsp_info) + fscsi->scsi_rqlen; icmd = fcp_icmd_alloc(pptr, ptgt, sizeof (struct fcp_cmd), rsp_len, fscsi->scsi_buflen, nodma, lcount, /* ipkt_link_cnt */ tcount, /* ipkt_change_cnt */ 0, /* cause */ FC_INVALID_RSCN_COUNT); /* invalidate the count */ if (icmd == NULL) { ret = ENOMEM; } else { /* * Setup internal packet as sema sync */ fcp_ipkt_sema_init(icmd); } } if (ret == 0) { /* * Init fpkt pointer for use. */ fpkt = icmd->ipkt_fpkt; fpkt->pkt_tran_flags = FC_TRAN_CLASS3 | FC_TRAN_INTR; fpkt->pkt_tran_type = FC_PKT_FCP_READ; /* only rd for now */ fpkt->pkt_timeout = fscsi->scsi_timeout; /* * Init fcmd pointer for use by SCSI command */ if (nodma) { fcmd = (struct fcp_cmd *)fpkt->pkt_cmd; } else { fcmd = &fcp_cmd; } bzero(fcmd, sizeof (struct fcp_cmd)); ptgt = plun->lun_tgt; lun_string = (uchar_t *)&fscsi->scsi_lun; fcmd->fcp_ent_addr.ent_addr_0 = BE_16(*(uint16_t *)&(lun_string[0])); fcmd->fcp_ent_addr.ent_addr_1 = BE_16(*(uint16_t *)&(lun_string[2])); fcmd->fcp_ent_addr.ent_addr_2 = BE_16(*(uint16_t *)&(lun_string[4])); fcmd->fcp_ent_addr.ent_addr_3 = BE_16(*(uint16_t *)&(lun_string[6])); /* * Setup internal packet(icmd) */ icmd->ipkt_lun = plun; icmd->ipkt_restart = 0; icmd->ipkt_retries = 0; icmd->ipkt_opcode = 0; /* * Init the frame HEADER Pointer for use */ hp = &fpkt->pkt_cmd_fhdr; hp->s_id = pptr->port_id; hp->d_id = ptgt->tgt_d_id; hp->r_ctl = R_CTL_COMMAND; hp->type = FC_TYPE_SCSI_FCP; hp->f_ctl = F_CTL_SEQ_INITIATIVE | F_CTL_FIRST_SEQ; hp->rsvd = 0; hp->seq_id = 0; hp->seq_cnt = 0; hp->ox_id = 0xffff; hp->rx_id = 0xffff; hp->ro = 0; fcmd->fcp_cntl.cntl_qtype = FCP_QTYPE_SIMPLE; fcmd->fcp_cntl.cntl_read_data = 1; /* only rd for now */ fcmd->fcp_cntl.cntl_write_data = 0; fcmd->fcp_data_len = fscsi->scsi_buflen; scsi_cdb = (union scsi_cdb *)fcmd->fcp_cdb; bcopy((char *)fscsi->scsi_cdbbufaddr, (char *)scsi_cdb, fscsi->scsi_cdblen); if (!nodma) { FCP_CP_OUT((uint8_t *)fcmd, fpkt->pkt_cmd, fpkt->pkt_cmd_acc, sizeof (struct fcp_cmd)); } /* * Send SCSI command to FC transport */ if (ret == 0) { mutex_enter(&ptgt->tgt_mutex); if (!FCP_TGT_STATE_CHANGED(ptgt, icmd)) { mutex_exit(&ptgt->tgt_mutex); fscsi->scsi_fc_status = xport_retval = fc_ulp_transport(pptr->port_fp_handle, fpkt); if (fscsi->scsi_fc_status != FC_SUCCESS) { ret = EIO; } } else { mutex_exit(&ptgt->tgt_mutex); ret = EBUSY; } } } /* * Wait for completion only if fc_ulp_transport was called and it * returned a success. This is the only time callback will happen. * Otherwise, there is no point in waiting */ if ((ret == 0) && (xport_retval == FC_SUCCESS)) { ret = fcp_ipkt_sema_wait(icmd); } /* * Copy data to IOCTL data structures */ rsp = NULL; if ((ret == 0) && (xport_retval == FC_SUCCESS)) { rsp = (struct fcp_rsp *)fpkt->pkt_resp; if (fcp_validate_fcp_response(rsp, pptr) != FC_SUCCESS) { fcp_log(CE_WARN, pptr->port_dip, "!SCSI command to d_id=0x%x lun=0x%x" " failed, Bad FCP response values:" " rsvd1=%x, rsvd2=%x, sts-rsvd1=%x," " sts-rsvd2=%x, rsplen=%x, senselen=%x", ptgt->tgt_d_id, plun->lun_num, rsp->reserved_0, rsp->reserved_1, rsp->fcp_u.fcp_status.reserved_0, rsp->fcp_u.fcp_status.reserved_1, rsp->fcp_response_len, rsp->fcp_sense_len); ret = EIO; } } if ((ret == 0) && (rsp != NULL)) { /* * Calc response lengths */ sense_len = 0; info_len = 0; if (rsp->fcp_u.fcp_status.rsp_len_set) { info_len = rsp->fcp_response_len; } rsp_info = (struct fcp_rsp_info *) ((uint8_t *)rsp + sizeof (struct fcp_rsp)); /* * Get SCSI status */ fscsi->scsi_bufstatus = rsp->fcp_u.fcp_status.scsi_status; /* * If a lun was just added or removed and the next command * comes through this interface, we need to capture the check * condition so we can discover the new topology. */ if (fscsi->scsi_bufstatus != STATUS_GOOD && rsp->fcp_u.fcp_status.sense_len_set) { sense_len = rsp->fcp_sense_len; rsp_sense = (caddr_t)((uint8_t *)rsp_info + info_len); sense_to = (struct scsi_extended_sense *)rsp_sense; if ((FCP_SENSE_REPORTLUN_CHANGED(sense_to)) || (FCP_SENSE_NO_LUN(sense_to))) { reconfig_lun = TRUE; } } if (fscsi->scsi_bufstatus == STATUS_GOOD && (ptgt != NULL) && (reconfig_lun || (scsi_cdb->scc_cmd == SCMD_REPORT_LUN))) { if (reconfig_lun == FALSE) { reconfig_status = fcp_is_reconfig_needed(ptgt, fpkt); } if ((reconfig_lun == TRUE) || (reconfig_status == TRUE)) { mutex_enter(&ptgt->tgt_mutex); if (ptgt->tgt_tid == NULL) { /* * Either we've been notified the * REPORT_LUN data has changed, or * we've determined on our own that * we're out of date. Kick off * rediscovery. */ tid = timeout(fcp_reconfigure_luns, (caddr_t)ptgt, drv_usectohz(1)); ptgt->tgt_tid = tid; ptgt->tgt_state |= FCP_TGT_BUSY; ret = EBUSY; reconfig_pending = TRUE; } mutex_exit(&ptgt->tgt_mutex); } } /* * Calc residuals and buffer lengths */ if (ret == 0) { buf_len = fscsi->scsi_buflen; fscsi->scsi_bufresid = 0; if (rsp->fcp_u.fcp_status.resid_under) { if (rsp->fcp_resid <= fscsi->scsi_buflen) { fscsi->scsi_bufresid = rsp->fcp_resid; } else { cmn_err(CE_WARN, "fcp: bad residue %x " "for txfer len %x", rsp->fcp_resid, fscsi->scsi_buflen); fscsi->scsi_bufresid = fscsi->scsi_buflen; } buf_len -= fscsi->scsi_bufresid; } if (rsp->fcp_u.fcp_status.resid_over) { fscsi->scsi_bufresid = -rsp->fcp_resid; } fscsi->scsi_rqresid = fscsi->scsi_rqlen - sense_len; if (fscsi->scsi_rqlen < sense_len) { sense_len = fscsi->scsi_rqlen; } fscsi->scsi_fc_rspcode = 0; if (rsp->fcp_u.fcp_status.rsp_len_set) { fscsi->scsi_fc_rspcode = rsp_info->rsp_code; } fscsi->scsi_pkt_state = fpkt->pkt_state; fscsi->scsi_pkt_action = fpkt->pkt_action; fscsi->scsi_pkt_reason = fpkt->pkt_reason; /* * Copy data and request sense * * Data must be copied by using the FCP_CP_IN macro. * This will ensure the proper byte order since the data * is being copied directly from the memory mapped * device register. * * The response (and request sense) will be in the * correct byte order. No special copy is necessary. */ if (buf_len) { FCP_CP_IN(fpkt->pkt_data, fscsi->scsi_bufaddr, fpkt->pkt_data_acc, buf_len); } bcopy((void *)rsp_sense, (void *)fscsi->scsi_rqbufaddr, sense_len); } } /* * Cleanup transport data structures if icmd was alloc-ed * So, cleanup happens in the same thread that icmd was alloc-ed */ if (icmd != NULL) { fcp_ipkt_sema_cleanup(icmd); } /* restore pm busy/idle status */ if (port_busy) { fc_ulp_idle_port(pptr->port_fp_handle); } /* * Cleanup target. if a reconfig is pending, don't clear the BUSY * flag, it'll be cleared when the reconfig is complete. */ if ((ptgt != NULL) && !reconfig_pending) { /* * If target was created, */ if (target_created) { mutex_enter(&ptgt->tgt_mutex); ptgt->tgt_state &= ~FCP_TGT_BUSY; mutex_exit(&ptgt->tgt_mutex); } else { /* * De-mark target as busy */ mutex_enter(&ptgt->tgt_mutex); ptgt->tgt_state &= ~FCP_TGT_BUSY; mutex_exit(&ptgt->tgt_mutex); } } return (ret); } static int fcp_is_reconfig_needed(struct fcp_tgt *ptgt, fc_packet_t *fpkt) { uchar_t *lun_string; uint16_t lun_num, i; int num_luns; int actual_luns; int num_masked_luns; int lun_buflen; struct fcp_lun *plun = NULL; struct fcp_reportlun_resp *report_lun; uint8_t reconfig_needed = FALSE; uint8_t lun_exists = FALSE; report_lun = kmem_zalloc(fpkt->pkt_datalen, KM_SLEEP); FCP_CP_IN(fpkt->pkt_data, report_lun, fpkt->pkt_data_acc, fpkt->pkt_datalen); /* get number of luns (which is supplied as LUNS * 8) */ num_luns = BE_32(report_lun->num_lun) >> 3; /* * Figure out exactly how many lun strings our response buffer * can hold. */ lun_buflen = (fpkt->pkt_datalen - 2 * sizeof (uint32_t)) / sizeof (longlong_t); /* * Is our response buffer full or not? We don't want to * potentially walk beyond the number of luns we have. */ if (num_luns <= lun_buflen) { actual_luns = num_luns; } else { actual_luns = lun_buflen; } mutex_enter(&ptgt->tgt_mutex); /* Scan each lun to see if we have masked it. */ num_masked_luns = 0; if (fcp_lun_blacklist != NULL) { for (i = 0; i < actual_luns; i++) { lun_string = (uchar_t *)&(report_lun->lun_string[i]); switch (lun_string[0] & 0xC0) { case FCP_LUN_ADDRESSING: case FCP_PD_ADDRESSING: lun_num = ((lun_string[0] & 0x3F) << 8) | lun_string[1]; if (fcp_should_mask(&ptgt->tgt_port_wwn, lun_num) == TRUE) { num_masked_luns++; } break; default: break; } } } /* * The quick and easy check. If the number of LUNs reported * doesn't match the number we currently know about, we need * to reconfigure. */ if (num_luns && num_luns != (ptgt->tgt_lun_cnt + num_masked_luns)) { mutex_exit(&ptgt->tgt_mutex); kmem_free(report_lun, fpkt->pkt_datalen); return (TRUE); } /* * If the quick and easy check doesn't turn up anything, we walk * the list of luns from the REPORT_LUN response and look for * any luns we don't know about. If we find one, we know we need * to reconfigure. We will skip LUNs that are masked because of the * blacklist. */ for (i = 0; i < actual_luns; i++) { lun_string = (uchar_t *)&(report_lun->lun_string[i]); lun_exists = FALSE; switch (lun_string[0] & 0xC0) { case FCP_LUN_ADDRESSING: case FCP_PD_ADDRESSING: lun_num = ((lun_string[0] & 0x3F) << 8) | lun_string[1]; if ((fcp_lun_blacklist != NULL) && (fcp_should_mask( &ptgt->tgt_port_wwn, lun_num) == TRUE)) { lun_exists = TRUE; break; } for (plun = ptgt->tgt_lun; plun; plun = plun->lun_next) { if (plun->lun_num == lun_num) { lun_exists = TRUE; break; } } break; default: break; } if (lun_exists == FALSE) { reconfig_needed = TRUE; break; } } mutex_exit(&ptgt->tgt_mutex); kmem_free(report_lun, fpkt->pkt_datalen); return (reconfig_needed); } /* * This function is called by fcp_handle_page83 and uses inquiry response data * stored in plun->lun_inq to determine whether or not a device is a member of * the table fcp_symmetric_disk_table_size. We return 0 if it is in the table, * otherwise 1. */ static int fcp_symmetric_device_probe(struct fcp_lun *plun) { struct scsi_inquiry *stdinq = &plun->lun_inq; char *devidptr; int i, len; for (i = 0; i < fcp_symmetric_disk_table_size; i++) { devidptr = fcp_symmetric_disk_table[i]; len = (int)strlen(devidptr); if (bcmp(stdinq->inq_vid, devidptr, len) == 0) { return (0); } } return (1); } /* * This function is called by fcp_ioctl for the FCP_STATE_COUNT ioctl * It basically returns the current count of # of state change callbacks * i.e the value of tgt_change_cnt. * * INPUT: * fcp_ioctl.fp_minor -> The minor # of the fp port * fcp_ioctl.listlen -> 1 * fcp_ioctl.list -> Pointer to a 32 bit integer */ /*ARGSUSED2*/ static int fcp_get_statec_count(struct fcp_ioctl *data, int mode, int *rval) { int ret; uint32_t link_cnt; struct fcp_ioctl fioctl; struct fcp_port *pptr = NULL; if ((ret = fcp_copyin_fcp_ioctl_data(data, mode, rval, &fioctl, &pptr)) != 0) { return (ret); } ASSERT(pptr != NULL); if (fioctl.listlen != 1) { return (EINVAL); } mutex_enter(&pptr->port_mutex); if (pptr->port_state & FCP_STATE_OFFLINE) { mutex_exit(&pptr->port_mutex); return (ENXIO); } /* * FCP_STATE_INIT is set in 2 cases (not sure why it is overloaded): * When the fcp initially attaches to the port and there are nothing * hanging out of the port or if there was a repeat offline state change * callback (refer fcp_statec_callback() FC_STATE_OFFLINE case). * In the latter case, port_tmp_cnt will be non-zero and that is how we * will differentiate the 2 cases. */ if ((pptr->port_state & FCP_STATE_INIT) && pptr->port_tmp_cnt) { mutex_exit(&pptr->port_mutex); return (ENXIO); } link_cnt = pptr->port_link_cnt; mutex_exit(&pptr->port_mutex); if (ddi_copyout(&link_cnt, fioctl.list, (sizeof (uint32_t)), mode)) { return (EFAULT); } #ifdef _MULTI_DATAMODEL switch (ddi_model_convert_from(mode & FMODELS)) { case DDI_MODEL_ILP32: { struct fcp32_ioctl f32_ioctl; f32_ioctl.fp_minor = fioctl.fp_minor; f32_ioctl.listlen = fioctl.listlen; f32_ioctl.list = (caddr32_t)(long)fioctl.list; if (ddi_copyout((void *)&f32_ioctl, (void *)data, sizeof (struct fcp32_ioctl), mode)) { return (EFAULT); } break; } case DDI_MODEL_NONE: if (ddi_copyout((void *)&fioctl, (void *)data, sizeof (struct fcp_ioctl), mode)) { return (EFAULT); } break; } #else /* _MULTI_DATAMODEL */ if (ddi_copyout((void *)&fioctl, (void *)data, sizeof (struct fcp_ioctl), mode)) { return (EFAULT); } #endif /* _MULTI_DATAMODEL */ return (0); } /* * This function copies the fcp_ioctl structure passed in from user land * into kernel land. Handles 32 bit applications. */ /*ARGSUSED*/ static int fcp_copyin_fcp_ioctl_data(struct fcp_ioctl *data, int mode, int *rval, struct fcp_ioctl *fioctl, struct fcp_port **pptr) { struct fcp_port *t_pptr; #ifdef _MULTI_DATAMODEL switch (ddi_model_convert_from(mode & FMODELS)) { case DDI_MODEL_ILP32: { struct fcp32_ioctl f32_ioctl; if (ddi_copyin((void *)data, (void *)&f32_ioctl, sizeof (struct fcp32_ioctl), mode)) { return (EFAULT); } fioctl->fp_minor = f32_ioctl.fp_minor; fioctl->listlen = f32_ioctl.listlen; fioctl->list = (caddr_t)(long)f32_ioctl.list; break; } case DDI_MODEL_NONE: if (ddi_copyin((void *)data, (void *)fioctl, sizeof (struct fcp_ioctl), mode)) { return (EFAULT); } break; } #else /* _MULTI_DATAMODEL */ if (ddi_copyin((void *)data, (void *)fioctl, sizeof (struct fcp_ioctl), mode)) { return (EFAULT); } #endif /* _MULTI_DATAMODEL */ /* * Right now we can assume that the minor number matches with * this instance of fp. If this changes we will need to * revisit this logic. */ mutex_enter(&fcp_global_mutex); t_pptr = fcp_port_head; while (t_pptr) { if (t_pptr->port_instance == (uint32_t)fioctl->fp_minor) { break; } else { t_pptr = t_pptr->port_next; } } *pptr = t_pptr; mutex_exit(&fcp_global_mutex); if (t_pptr == NULL) { return (ENXIO); } return (0); } /* * Function: fcp_port_create_tgt * * Description: As the name suggest this function creates the target context * specified by the the WWN provided by the caller. If the * creation goes well and the target is known by fp/fctl a PLOGI * followed by a PRLI are issued. * * Argument: pptr fcp port structure * pwwn WWN of the target * ret_val Address of the return code. It could be: * EIO, ENOMEM or 0. * fc_status PLOGI or PRLI status completion * fc_pkt_state PLOGI or PRLI state completion * fc_pkt_reason PLOGI or PRLI reason completion * fc_pkt_action PLOGI or PRLI action completion * * Return Value: NULL if it failed * Target structure address if it succeeds */ static struct fcp_tgt * fcp_port_create_tgt(struct fcp_port *pptr, la_wwn_t *pwwn, int *ret_val, int *fc_status, int *fc_pkt_state, int *fc_pkt_reason, int *fc_pkt_action) { struct fcp_tgt *ptgt = NULL; fc_portmap_t devlist; int lcount; int error; *ret_val = 0; /* * Check FC port device & get port map */ if (fc_ulp_get_remote_port(pptr->port_fp_handle, pwwn, &error, 1) == NULL) { *ret_val = EIO; } else { if (fc_ulp_pwwn_to_portmap(pptr->port_fp_handle, pwwn, &devlist) != FC_SUCCESS) { *ret_val = EIO; } } /* Set port map flags */ devlist.map_type = PORT_DEVICE_USER_CREATE; /* Allocate target */ if (*ret_val == 0) { lcount = pptr->port_link_cnt; ptgt = fcp_alloc_tgt(pptr, &devlist, lcount); if (ptgt == NULL) { fcp_log(CE_WARN, pptr->port_dip, "!FC target allocation failed"); *ret_val = ENOMEM; } else { /* Setup target */ mutex_enter(&ptgt->tgt_mutex); ptgt->tgt_statec_cause = FCP_CAUSE_TGT_CHANGE; ptgt->tgt_tmp_cnt = 1; ptgt->tgt_d_id = devlist.map_did.port_id; ptgt->tgt_hard_addr = devlist.map_hard_addr.hard_addr; ptgt->tgt_pd_handle = devlist.map_pd; ptgt->tgt_fca_dev = NULL; bcopy(&devlist.map_nwwn, &ptgt->tgt_node_wwn.raw_wwn[0], FC_WWN_SIZE); bcopy(&devlist.map_pwwn, &ptgt->tgt_port_wwn.raw_wwn[0], FC_WWN_SIZE); mutex_exit(&ptgt->tgt_mutex); } } /* Release global mutex for PLOGI and PRLI */ mutex_exit(&fcp_global_mutex); /* Send PLOGI (If necessary) */ if (*ret_val == 0) { *ret_val = fcp_tgt_send_plogi(ptgt, fc_status, fc_pkt_state, fc_pkt_reason, fc_pkt_action); } /* Send PRLI (If necessary) */ if (*ret_val == 0) { *ret_val = fcp_tgt_send_prli(ptgt, fc_status, fc_pkt_state, fc_pkt_reason, fc_pkt_action); } mutex_enter(&fcp_global_mutex); return (ptgt); } /* * Function: fcp_tgt_send_plogi * * Description: This function sends a PLOGI to the target specified by the * caller and waits till it completes. * * Argument: ptgt Target to send the plogi to. * fc_status Status returned by fp/fctl in the PLOGI request. * fc_pkt_state State returned by fp/fctl in the PLOGI request. * fc_pkt_reason Reason returned by fp/fctl in the PLOGI request. * fc_pkt_action Action returned by fp/fctl in the PLOGI request. * * Return Value: 0 * ENOMEM * EIO * * Context: User context. */ static int fcp_tgt_send_plogi(struct fcp_tgt *ptgt, int *fc_status, int *fc_pkt_state, int *fc_pkt_reason, int *fc_pkt_action) { struct fcp_port *pptr; struct fcp_ipkt *icmd; struct fc_packet *fpkt; fc_frame_hdr_t *hp; struct la_els_logi logi; int tcount; int lcount; int ret, login_retval = ~FC_SUCCESS; ret = 0; pptr = ptgt->tgt_port; lcount = pptr->port_link_cnt; tcount = ptgt->tgt_change_cnt; /* Alloc internal packet */ icmd = fcp_icmd_alloc(pptr, ptgt, sizeof (la_els_logi_t), sizeof (la_els_logi_t), 0, 0, lcount, tcount, 0, FC_INVALID_RSCN_COUNT); if (icmd == NULL) { ret = ENOMEM; } else { /* * Setup internal packet as sema sync */ fcp_ipkt_sema_init(icmd); /* * Setup internal packet (icmd) */ icmd->ipkt_lun = NULL; icmd->ipkt_restart = 0; icmd->ipkt_retries = 0; icmd->ipkt_opcode = LA_ELS_PLOGI; /* * Setup fc_packet */ fpkt = icmd->ipkt_fpkt; fpkt->pkt_tran_flags = FC_TRAN_CLASS3 | FC_TRAN_INTR; fpkt->pkt_tran_type = FC_PKT_EXCHANGE; fpkt->pkt_timeout = FCP_ELS_TIMEOUT; /* * Setup FC frame header */ hp = &fpkt->pkt_cmd_fhdr; hp->s_id = pptr->port_id; /* source ID */ hp->d_id = ptgt->tgt_d_id; /* dest ID */ hp->r_ctl = R_CTL_ELS_REQ; hp->type = FC_TYPE_EXTENDED_LS; hp->f_ctl = F_CTL_SEQ_INITIATIVE | F_CTL_FIRST_SEQ; hp->seq_id = 0; hp->rsvd = 0; hp->df_ctl = 0; hp->seq_cnt = 0; hp->ox_id = 0xffff; /* i.e. none */ hp->rx_id = 0xffff; /* i.e. none */ hp->ro = 0; /* * Setup PLOGI */ bzero(&logi, sizeof (struct la_els_logi)); logi.ls_code.ls_code = LA_ELS_PLOGI; FCP_CP_OUT((uint8_t *)&logi, fpkt->pkt_cmd, fpkt->pkt_cmd_acc, sizeof (struct la_els_logi)); /* * Send PLOGI */ *fc_status = login_retval = fc_ulp_login(pptr->port_fp_handle, &fpkt, 1); if (*fc_status != FC_SUCCESS) { ret = EIO; } } /* * Wait for completion */ if ((ret == 0) && (login_retval == FC_SUCCESS)) { ret = fcp_ipkt_sema_wait(icmd); *fc_pkt_state = fpkt->pkt_state; *fc_pkt_reason = fpkt->pkt_reason; *fc_pkt_action = fpkt->pkt_action; } /* * Cleanup transport data structures if icmd was alloc-ed AND if there * is going to be no callback (i.e if fc_ulp_login() failed). * Otherwise, cleanup happens in callback routine. */ if (icmd != NULL) { fcp_ipkt_sema_cleanup(icmd); } return (ret); } /* * Function: fcp_tgt_send_prli * * Description: Does nothing as of today. * * Argument: ptgt Target to send the prli to. * fc_status Status returned by fp/fctl in the PRLI request. * fc_pkt_state State returned by fp/fctl in the PRLI request. * fc_pkt_reason Reason returned by fp/fctl in the PRLI request. * fc_pkt_action Action returned by fp/fctl in the PRLI request. * * Return Value: 0 */ /*ARGSUSED*/ static int fcp_tgt_send_prli(struct fcp_tgt *ptgt, int *fc_status, int *fc_pkt_state, int *fc_pkt_reason, int *fc_pkt_action) { return (0); } /* * Function: fcp_ipkt_sema_init * * Description: Initializes the semaphore contained in the internal packet. * * Argument: icmd Internal packet the semaphore of which must be * initialized. * * Return Value: None * * Context: User context only. */ static void fcp_ipkt_sema_init(struct fcp_ipkt *icmd) { struct fc_packet *fpkt; fpkt = icmd->ipkt_fpkt; /* Create semaphore for sync */ sema_init(&(icmd->ipkt_sema), 0, NULL, SEMA_DRIVER, NULL); /* Setup the completion callback */ fpkt->pkt_comp = fcp_ipkt_sema_callback; } /* * Function: fcp_ipkt_sema_wait * * Description: Wait on the semaphore embedded in the internal packet. The * semaphore is released in the callback. * * Argument: icmd Internal packet to wait on for completion. * * Return Value: 0 * EIO * EBUSY * EAGAIN * * Context: User context only. * * This function does a conversion between the field pkt_state of the fc_packet * embedded in the internal packet (icmd) and the code it returns. */ static int fcp_ipkt_sema_wait(struct fcp_ipkt *icmd) { struct fc_packet *fpkt; int ret; ret = EIO; fpkt = icmd->ipkt_fpkt; /* * Wait on semaphore */ sema_p(&(icmd->ipkt_sema)); /* * Check the status of the FC packet */ switch (fpkt->pkt_state) { case FC_PKT_SUCCESS: ret = 0; break; case FC_PKT_LOCAL_RJT: switch (fpkt->pkt_reason) { case FC_REASON_SEQ_TIMEOUT: case FC_REASON_RX_BUF_TIMEOUT: ret = EAGAIN; break; case FC_REASON_PKT_BUSY: ret = EBUSY; break; } break; case FC_PKT_TIMEOUT: ret = EAGAIN; break; case FC_PKT_LOCAL_BSY: case FC_PKT_TRAN_BSY: case FC_PKT_NPORT_BSY: case FC_PKT_FABRIC_BSY: ret = EBUSY; break; case FC_PKT_LS_RJT: case FC_PKT_BA_RJT: switch (fpkt->pkt_reason) { case FC_REASON_LOGICAL_BSY: ret = EBUSY; break; } break; case FC_PKT_FS_RJT: switch (fpkt->pkt_reason) { case FC_REASON_FS_LOGICAL_BUSY: ret = EBUSY; break; } break; } return (ret); } /* * Function: fcp_ipkt_sema_callback * * Description: Registered as the completion callback function for the FC * transport when the ipkt semaphore is used for sync. This will * cleanup the used data structures, if necessary and wake up * the user thread to complete the transaction. * * Argument: fpkt FC packet (points to the icmd) * * Return Value: None * * Context: User context only */ static void fcp_ipkt_sema_callback(struct fc_packet *fpkt) { struct fcp_ipkt *icmd; icmd = (struct fcp_ipkt *)fpkt->pkt_ulp_private; /* * Wake up user thread */ sema_v(&(icmd->ipkt_sema)); } /* * Function: fcp_ipkt_sema_cleanup * * Description: Called to cleanup (if necessary) the data structures used * when ipkt sema is used for sync. This function will detect * whether the caller is the last thread (via counter) and * cleanup only if necessary. * * Argument: icmd Internal command packet * * Return Value: None * * Context: User context only */ static void fcp_ipkt_sema_cleanup(struct fcp_ipkt *icmd) { struct fcp_tgt *ptgt; struct fcp_port *pptr; ptgt = icmd->ipkt_tgt; pptr = icmd->ipkt_port; /* * Acquire data structure */ mutex_enter(&ptgt->tgt_mutex); /* * Destroy semaphore */ sema_destroy(&(icmd->ipkt_sema)); /* * Cleanup internal packet */ mutex_exit(&ptgt->tgt_mutex); fcp_icmd_free(pptr, icmd); } /* * Function: fcp_port_attach * * Description: Called by the transport framework to resume, suspend or * attach a new port. * * Argument: ulph Port handle * *pinfo Port information * cmd Command * s_id Port ID * * Return Value: FC_FAILURE or FC_SUCCESS */ /*ARGSUSED*/ static int fcp_port_attach(opaque_t ulph, fc_ulp_port_info_t *pinfo, fc_attach_cmd_t cmd, uint32_t s_id) { int instance; int res = FC_FAILURE; /* default result */ ASSERT(pinfo != NULL); instance = ddi_get_instance(pinfo->port_dip); switch (cmd) { case FC_CMD_ATTACH: /* * this port instance attaching for the first time (or after * being detached before) */ if (fcp_handle_port_attach(ulph, pinfo, s_id, instance) == DDI_SUCCESS) { res = FC_SUCCESS; } else { ASSERT(ddi_get_soft_state(fcp_softstate, instance) == NULL); } break; case FC_CMD_RESUME: case FC_CMD_POWER_UP: /* * this port instance was attached and the suspended and * will now be resumed */ if (fcp_handle_port_resume(ulph, pinfo, s_id, cmd, instance) == DDI_SUCCESS) { res = FC_SUCCESS; } break; default: /* shouldn't happen */ FCP_TRACE(fcp_logq, "fcp", fcp_trace, FCP_BUF_LEVEL_2, 0, "port_attach: unknown cmdcommand: %d", cmd); break; } /* return result */ FCP_DTRACE(fcp_logq, "fcp", fcp_trace, FCP_BUF_LEVEL_1, 0, "fcp_port_attach returning %d", res); return (res); } /* * detach or suspend this port instance * * acquires and releases the global mutex * * acquires and releases the mutex for this port * * acquires and releases the hotplug mutex for this port */ /*ARGSUSED*/ static int fcp_port_detach(opaque_t ulph, fc_ulp_port_info_t *info, fc_detach_cmd_t cmd) { int flag; int instance; struct fcp_port *pptr; instance = ddi_get_instance(info->port_dip); pptr = ddi_get_soft_state(fcp_softstate, instance); switch (cmd) { case FC_CMD_SUSPEND: FCP_DTRACE(fcp_logq, "fcp", fcp_trace, FCP_BUF_LEVEL_8, 0, "port suspend called for port %d", instance); flag = FCP_STATE_SUSPENDED; break; case FC_CMD_POWER_DOWN: FCP_DTRACE(fcp_logq, "fcp", fcp_trace, FCP_BUF_LEVEL_8, 0, "port power down called for port %d", instance); flag = FCP_STATE_POWER_DOWN; break; case FC_CMD_DETACH: FCP_DTRACE(fcp_logq, "fcp", fcp_trace, FCP_BUF_LEVEL_8, 0, "port detach called for port %d", instance); flag = FCP_STATE_DETACHING; break; default: /* shouldn't happen */ return (FC_FAILURE); } FCP_DTRACE(fcp_logq, "fcp", fcp_trace, FCP_BUF_LEVEL_1, 0, "fcp_port_detach returning"); return (fcp_handle_port_detach(pptr, flag, instance)); } /* * called for ioctls on the transport's devctl interface, and the transport * has passed it to us * * this will only be called for device control ioctls (i.e. hotplugging stuff) * * return FC_SUCCESS if we decide to claim the ioctl, * else return FC_UNCLAIMED * * *rval is set iff we decide to claim the ioctl */ /*ARGSUSED*/ static int fcp_port_ioctl(opaque_t ulph, opaque_t port_handle, dev_t dev, int cmd, intptr_t data, int mode, cred_t *credp, int *rval, uint32_t claimed) { int retval = FC_UNCLAIMED; /* return value */ struct fcp_port *pptr = NULL; /* our soft state */ struct devctl_iocdata *dcp = NULL; /* for devctl */ dev_info_t *cdip; mdi_pathinfo_t *pip = NULL; char *ndi_nm; /* NDI name */ char *ndi_addr; /* NDI addr */ int is_mpxio, circ; int devi_entered = 0; time_t end_time; ASSERT(rval != NULL); FCP_DTRACE(fcp_logq, "fcp", fcp_trace, FCP_BUF_LEVEL_8, 0, "fcp_port_ioctl(cmd=0x%x, claimed=%d)", cmd, claimed); /* if already claimed then forget it */ if (claimed) { /* * for now, if this ioctl has already been claimed, then * we just ignore it */ return (retval); } /* get our port info */ if ((pptr = fcp_get_port(port_handle)) == NULL) { fcp_log(CE_WARN, NULL, "!fcp:Invalid port handle handle in ioctl"); *rval = ENXIO; return (retval); } is_mpxio = pptr->port_mpxio; switch (cmd) { case DEVCTL_BUS_GETSTATE: case DEVCTL_BUS_QUIESCE: case DEVCTL_BUS_UNQUIESCE: case DEVCTL_BUS_RESET: case DEVCTL_BUS_RESETALL: case DEVCTL_BUS_DEV_CREATE: if (ndi_dc_allochdl((void *)data, &dcp) != NDI_SUCCESS) { return (retval); } break; case DEVCTL_DEVICE_GETSTATE: case DEVCTL_DEVICE_OFFLINE: case DEVCTL_DEVICE_ONLINE: case DEVCTL_DEVICE_REMOVE: case DEVCTL_DEVICE_RESET: if (ndi_dc_allochdl((void *)data, &dcp) != NDI_SUCCESS) { return (retval); } ASSERT(dcp != NULL); /* ensure we have a name and address */ if (((ndi_nm = ndi_dc_getname(dcp)) == NULL) || ((ndi_addr = ndi_dc_getaddr(dcp)) == NULL)) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "ioctl: can't get name (%s) or addr (%s)", ndi_nm ? ndi_nm : "", ndi_addr ? ndi_addr : ""); ndi_dc_freehdl(dcp); return (retval); } /* get our child's DIP */ ASSERT(pptr != NULL); if (is_mpxio) { mdi_devi_enter(pptr->port_dip, &circ); } else { ndi_devi_enter(pptr->port_dip, &circ); } devi_entered = 1; if ((cdip = ndi_devi_find(pptr->port_dip, ndi_nm, ndi_addr)) == NULL) { /* Look for virtually enumerated devices. */ pip = mdi_pi_find(pptr->port_dip, NULL, ndi_addr); if (pip == NULL || ((cdip = mdi_pi_get_client(pip)) == NULL)) { *rval = ENXIO; goto out; } } break; default: *rval = ENOTTY; return (retval); } /* this ioctl is ours -- process it */ retval = FC_SUCCESS; /* just means we claim the ioctl */ /* we assume it will be a success; else we'll set error value */ *rval = 0; FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_8, 0, "ioctl: claiming this one"); /* handle ioctls now */ switch (cmd) { case DEVCTL_DEVICE_GETSTATE: ASSERT(cdip != NULL); ASSERT(dcp != NULL); if (ndi_dc_return_dev_state(cdip, dcp) != NDI_SUCCESS) { *rval = EFAULT; } break; case DEVCTL_DEVICE_REMOVE: case DEVCTL_DEVICE_OFFLINE: { int flag = 0; int lcount; int tcount; struct fcp_pkt *head = NULL; struct fcp_lun *plun; child_info_t *cip = CIP(cdip); int all = 1; struct fcp_lun *tplun; struct fcp_tgt *ptgt; ASSERT(pptr != NULL); ASSERT(cdip != NULL); mutex_enter(&pptr->port_mutex); if (pip != NULL) { cip = CIP(pip); } if ((plun = fcp_get_lun_from_cip(pptr, cip)) == NULL) { mutex_exit(&pptr->port_mutex); *rval = ENXIO; break; } head = fcp_scan_commands(plun); if (head != NULL) { fcp_abort_commands(head, LUN_PORT); } lcount = pptr->port_link_cnt; tcount = plun->lun_tgt->tgt_change_cnt; mutex_exit(&pptr->port_mutex); if (cmd == DEVCTL_DEVICE_REMOVE) { flag = NDI_DEVI_REMOVE; } if (is_mpxio) { mdi_devi_exit(pptr->port_dip, circ); } else { ndi_devi_exit(pptr->port_dip, circ); } devi_entered = 0; *rval = fcp_pass_to_hp_and_wait(pptr, plun, cip, FCP_OFFLINE, lcount, tcount, flag); if (*rval != NDI_SUCCESS) { *rval = (*rval == NDI_BUSY) ? EBUSY : EIO; break; } fcp_update_offline_flags(plun); ptgt = plun->lun_tgt; mutex_enter(&ptgt->tgt_mutex); for (tplun = ptgt->tgt_lun; tplun != NULL; tplun = tplun->lun_next) { mutex_enter(&tplun->lun_mutex); if (!(tplun->lun_state & FCP_LUN_OFFLINE)) { all = 0; } mutex_exit(&tplun->lun_mutex); } if (all) { ptgt->tgt_node_state = FCP_TGT_NODE_NONE; /* * The user is unconfiguring/offlining the device. * If fabric and the auto configuration is set * then make sure the user is the only one who * can reconfigure the device. */ if (FC_TOP_EXTERNAL(pptr->port_topology) && fcp_enable_auto_configuration) { ptgt->tgt_manual_config_only = 1; } } mutex_exit(&ptgt->tgt_mutex); break; } case DEVCTL_DEVICE_ONLINE: { int lcount; int tcount; struct fcp_lun *plun; child_info_t *cip = CIP(cdip); ASSERT(cdip != NULL); ASSERT(pptr != NULL); mutex_enter(&pptr->port_mutex); if (pip != NULL) { cip = CIP(pip); } if ((plun = fcp_get_lun_from_cip(pptr, cip)) == NULL) { mutex_exit(&pptr->port_mutex); *rval = ENXIO; break; } lcount = pptr->port_link_cnt; tcount = plun->lun_tgt->tgt_change_cnt; mutex_exit(&pptr->port_mutex); /* * The FCP_LUN_ONLINING flag is used in fcp_scsi_start() * to allow the device attach to occur when the device is * FCP_LUN_OFFLINE (so we don't reject the INQUIRY command * from the scsi_probe()). */ mutex_enter(&LUN_TGT->tgt_mutex); plun->lun_state |= FCP_LUN_ONLINING; mutex_exit(&LUN_TGT->tgt_mutex); if (is_mpxio) { mdi_devi_exit(pptr->port_dip, circ); } else { ndi_devi_exit(pptr->port_dip, circ); } devi_entered = 0; *rval = fcp_pass_to_hp_and_wait(pptr, plun, cip, FCP_ONLINE, lcount, tcount, 0); if (*rval != NDI_SUCCESS) { /* Reset the FCP_LUN_ONLINING bit */ mutex_enter(&LUN_TGT->tgt_mutex); plun->lun_state &= ~FCP_LUN_ONLINING; mutex_exit(&LUN_TGT->tgt_mutex); *rval = EIO; break; } mutex_enter(&LUN_TGT->tgt_mutex); plun->lun_state &= ~(FCP_LUN_OFFLINE | FCP_LUN_BUSY | FCP_LUN_ONLINING); mutex_exit(&LUN_TGT->tgt_mutex); break; } case DEVCTL_BUS_DEV_CREATE: { uchar_t *bytes = NULL; uint_t nbytes; struct fcp_tgt *ptgt = NULL; struct fcp_lun *plun = NULL; dev_info_t *useless_dip = NULL; *rval = ndi_dc_devi_create(dcp, pptr->port_dip, DEVCTL_CONSTRUCT, &useless_dip); if (*rval != 0 || useless_dip == NULL) { break; } if ((ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, useless_dip, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, PORT_WWN_PROP, &bytes, &nbytes) != DDI_PROP_SUCCESS) || nbytes != FC_WWN_SIZE) { *rval = EINVAL; (void) ndi_devi_free(useless_dip); if (bytes != NULL) { ddi_prop_free(bytes); } break; } *rval = fcp_create_on_demand(pptr, bytes); if (*rval == 0) { mutex_enter(&pptr->port_mutex); ptgt = fcp_lookup_target(pptr, (uchar_t *)bytes); if (ptgt) { /* * We now have a pointer to the target that * was created. Lets point to the first LUN on * this new target. */ mutex_enter(&ptgt->tgt_mutex); plun = ptgt->tgt_lun; /* * There may be stale/offline LUN entries on * this list (this is by design) and so we have * to make sure we point to the first online * LUN */ while (plun && plun->lun_state & FCP_LUN_OFFLINE) { plun = plun->lun_next; } mutex_exit(&ptgt->tgt_mutex); } mutex_exit(&pptr->port_mutex); } if (*rval == 0 && ptgt && plun) { mutex_enter(&plun->lun_mutex); /* * Allow up to fcp_lun_ready_retry seconds to * configure all the luns behind the target. * * The intent here is to allow targets with long * reboot/reset-recovery times to become available * while limiting the maximum wait time for an * unresponsive target. */ end_time = ddi_get_lbolt() + SEC_TO_TICK(fcp_lun_ready_retry); while (ddi_get_lbolt() < end_time) { retval = FC_SUCCESS; /* * The new ndi interfaces for on-demand creation * are inflexible, Do some more work to pass on * a path name of some LUN (design is broken !) */ if (plun->lun_cip) { if (plun->lun_mpxio == 0) { cdip = DIP(plun->lun_cip); } else { cdip = mdi_pi_get_client( PIP(plun->lun_cip)); } if (cdip == NULL) { *rval = ENXIO; break; } if (!i_ddi_devi_attached(cdip)) { mutex_exit(&plun->lun_mutex); delay(drv_usectohz(1000000)); mutex_enter(&plun->lun_mutex); } else { /* * This Lun is ready, lets * check the next one. */ mutex_exit(&plun->lun_mutex); plun = plun->lun_next; while (plun && (plun->lun_state & FCP_LUN_OFFLINE)) { plun = plun->lun_next; } if (!plun) { break; } mutex_enter(&plun->lun_mutex); } } else { /* * lun_cip field for a valid lun * should never be NULL. Fail the * command. */ *rval = ENXIO; break; } } if (plun) { mutex_exit(&plun->lun_mutex); } else { char devnm[MAXNAMELEN]; int nmlen; nmlen = snprintf(devnm, MAXNAMELEN, "%s@%s", ddi_node_name(cdip), ddi_get_name_addr(cdip)); if (copyout(&devnm, dcp->cpyout_buf, nmlen) != 0) { *rval = EFAULT; } } } else { int i; char buf[25]; for (i = 0; i < FC_WWN_SIZE; i++) { (void) sprintf(&buf[i << 1], "%02x", bytes[i]); } fcp_log(CE_WARN, pptr->port_dip, "!Failed to create nodes for pwwn=%s; error=%x", buf, *rval); } (void) ndi_devi_free(useless_dip); ddi_prop_free(bytes); break; } case DEVCTL_DEVICE_RESET: { struct fcp_lun *plun; struct scsi_address ap; child_info_t *cip = CIP(cdip); ASSERT(cdip != NULL); ASSERT(pptr != NULL); mutex_enter(&pptr->port_mutex); if (pip != NULL) { cip = CIP(pip); } if ((plun = fcp_get_lun_from_cip(pptr, cip)) == NULL) { mutex_exit(&pptr->port_mutex); *rval = ENXIO; break; } mutex_exit(&pptr->port_mutex); mutex_enter(&plun->lun_tgt->tgt_mutex); if (!(plun->lun_state & FCP_SCSI_LUN_TGT_INIT)) { mutex_exit(&plun->lun_tgt->tgt_mutex); *rval = ENXIO; break; } ap.a_hba_tran = plun->lun_tran; ASSERT(pptr->port_tran != NULL); mutex_exit(&plun->lun_tgt->tgt_mutex); /* * There is a chance lun_tran is NULL at this point. So check * for it. If it is NULL, it basically means that the tgt has * been freed. So, just return a "No such device or address" * error. */ if (ap.a_hba_tran == NULL) { *rval = ENXIO; break; } /* * set up ap so that fcp_reset can figure out * which target to reset */ if (fcp_scsi_reset(&ap, RESET_TARGET) == FALSE) { *rval = EIO; } break; } case DEVCTL_BUS_GETSTATE: ASSERT(dcp != NULL); ASSERT(pptr != NULL); ASSERT(pptr->port_dip != NULL); if (ndi_dc_return_bus_state(pptr->port_dip, dcp) != NDI_SUCCESS) { *rval = EFAULT; } break; case DEVCTL_BUS_QUIESCE: case DEVCTL_BUS_UNQUIESCE: *rval = ENOTSUP; break; case DEVCTL_BUS_RESET: case DEVCTL_BUS_RESETALL: ASSERT(pptr != NULL); (void) fcp_linkreset(pptr, NULL, KM_SLEEP); break; default: ASSERT(dcp != NULL); *rval = ENOTTY; break; } /* all done -- clean up and return */ out: if (devi_entered) { if (is_mpxio) { mdi_devi_exit(pptr->port_dip, circ); } else { ndi_devi_exit(pptr->port_dip, circ); } } if (dcp != NULL) { ndi_dc_freehdl(dcp); } return (retval); } /*ARGSUSED*/ static int fcp_els_callback(opaque_t ulph, opaque_t port_handle, fc_unsol_buf_t *buf, uint32_t claimed) { uchar_t r_ctl; uchar_t ls_code; struct fcp_port *pptr; if ((pptr = fcp_get_port(port_handle)) == NULL || claimed) { return (FC_UNCLAIMED); } mutex_enter(&pptr->port_mutex); if (pptr->port_state & (FCP_STATE_DETACHING | FCP_STATE_SUSPENDED | FCP_STATE_POWER_DOWN)) { mutex_exit(&pptr->port_mutex); return (FC_UNCLAIMED); } mutex_exit(&pptr->port_mutex); r_ctl = buf->ub_frame.r_ctl; switch (r_ctl & R_CTL_ROUTING) { case R_CTL_EXTENDED_SVC: if (r_ctl == R_CTL_ELS_REQ) { ls_code = buf->ub_buffer[0]; switch (ls_code) { case LA_ELS_PRLI: /* * We really don't care if something fails. * If the PRLI was not sent out, then the * other end will time it out. */ if (fcp_unsol_prli(pptr, buf) == FC_SUCCESS) { return (FC_SUCCESS); } return (FC_UNCLAIMED); /* NOTREACHED */ default: break; } } /* FALLTHROUGH */ default: return (FC_UNCLAIMED); } } /*ARGSUSED*/ static int fcp_data_callback(opaque_t ulph, opaque_t port_handle, fc_unsol_buf_t *buf, uint32_t claimed) { return (FC_UNCLAIMED); } /* * Function: fcp_statec_callback * * Description: The purpose of this function is to handle a port state change. * It is called from fp/fctl and, in a few instances, internally. * * Argument: ulph fp/fctl port handle * port_handle fcp_port structure * port_state Physical state of the port * port_top Topology * *devlist Pointer to the first entry of a table * containing the remote ports that can be * reached. * dev_cnt Number of entries pointed by devlist. * port_sid Port ID of the local port. * * Return Value: None */ /*ARGSUSED*/ static void fcp_statec_callback(opaque_t ulph, opaque_t port_handle, uint32_t port_state, uint32_t port_top, fc_portmap_t *devlist, uint32_t dev_cnt, uint32_t port_sid) { uint32_t link_count; int map_len = 0; struct fcp_port *pptr; fcp_map_tag_t *map_tag = NULL; if ((pptr = fcp_get_port(port_handle)) == NULL) { fcp_log(CE_WARN, NULL, "!Invalid port handle in callback"); return; /* nothing to work with! */ } FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_statec_callback: port state/dev_cnt/top =" "%d/%d/%d", FC_PORT_STATE_MASK(port_state), dev_cnt, port_top); mutex_enter(&pptr->port_mutex); /* * If a thread is in detach, don't do anything. */ if (pptr->port_state & (FCP_STATE_DETACHING | FCP_STATE_SUSPENDED | FCP_STATE_POWER_DOWN)) { mutex_exit(&pptr->port_mutex); return; } /* * First thing we do is set the FCP_STATE_IN_CB_DEVC flag so that if * init_pkt is called, it knows whether or not the target's status * (or pd) might be changing. */ if (FC_PORT_STATE_MASK(port_state) == FC_STATE_DEVICE_CHANGE) { pptr->port_state |= FCP_STATE_IN_CB_DEVC; } /* * the transport doesn't allocate or probe unless being * asked to by either the applications or ULPs * * in cases where the port is OFFLINE at the time of port * attach callback and the link comes ONLINE later, for * easier automatic node creation (i.e. without you having to * go out and run the utility to perform LOGINs) the * following conditional is helpful */ pptr->port_phys_state = port_state; if (dev_cnt) { mutex_exit(&pptr->port_mutex); map_len = sizeof (*map_tag) * dev_cnt; map_tag = kmem_alloc(map_len, KM_NOSLEEP); if (map_tag == NULL) { fcp_log(CE_WARN, pptr->port_dip, "!fcp%d: failed to allocate for map tags; " " state change will not be processed", pptr->port_instance); mutex_enter(&pptr->port_mutex); pptr->port_state &= ~FCP_STATE_IN_CB_DEVC; mutex_exit(&pptr->port_mutex); return; } mutex_enter(&pptr->port_mutex); } if (pptr->port_id != port_sid) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp: Port S_ID=0x%x => 0x%x", pptr->port_id, port_sid); /* * The local port changed ID. It is the first time a port ID * is assigned or something drastic happened. We might have * been unplugged and replugged on another loop or fabric port * or somebody grabbed the AL_PA we had or somebody rezoned * the fabric we were plugged into. */ pptr->port_id = port_sid; } switch (FC_PORT_STATE_MASK(port_state)) { case FC_STATE_OFFLINE: case FC_STATE_RESET_REQUESTED: /* * link has gone from online to offline -- just update the * state of this port to BUSY and MARKed to go offline */ FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "link went offline"); if ((pptr->port_state & FCP_STATE_OFFLINE) && dev_cnt) { /* * We were offline a while ago and this one * seems to indicate that the loop has gone * dead forever. */ pptr->port_tmp_cnt += dev_cnt; pptr->port_state &= ~FCP_STATE_OFFLINE; pptr->port_state |= FCP_STATE_INIT; link_count = pptr->port_link_cnt; fcp_handle_devices(pptr, devlist, dev_cnt, link_count, map_tag, FCP_CAUSE_LINK_DOWN); } else { pptr->port_link_cnt++; ASSERT(!(pptr->port_state & FCP_STATE_SUSPENDED)); fcp_update_state(pptr, (FCP_LUN_BUSY | FCP_LUN_MARK), FCP_CAUSE_LINK_DOWN); if (pptr->port_mpxio) { fcp_update_mpxio_path_verifybusy(pptr); } pptr->port_state |= FCP_STATE_OFFLINE; pptr->port_state &= ~(FCP_STATE_ONLINING | FCP_STATE_ONLINE); pptr->port_tmp_cnt = 0; } mutex_exit(&pptr->port_mutex); break; case FC_STATE_ONLINE: case FC_STATE_LIP: case FC_STATE_LIP_LBIT_SET: /* * link has gone from offline to online */ FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "link went online"); pptr->port_link_cnt++; while (pptr->port_ipkt_cnt) { mutex_exit(&pptr->port_mutex); delay(drv_usectohz(1000000)); mutex_enter(&pptr->port_mutex); } pptr->port_topology = port_top; /* * The state of the targets and luns accessible through this * port is updated. */ fcp_update_state(pptr, FCP_LUN_BUSY | FCP_LUN_MARK, FCP_CAUSE_LINK_CHANGE); pptr->port_state &= ~(FCP_STATE_INIT | FCP_STATE_OFFLINE); pptr->port_state |= FCP_STATE_ONLINING; pptr->port_tmp_cnt = dev_cnt; link_count = pptr->port_link_cnt; pptr->port_deadline = fcp_watchdog_time + FCP_ICMD_DEADLINE; if (!dev_cnt) { /* * We go directly to the online state if no remote * ports were discovered. */ FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "No remote ports discovered"); pptr->port_state &= ~FCP_STATE_ONLINING; pptr->port_state |= FCP_STATE_ONLINE; } switch (port_top) { case FC_TOP_FABRIC: case FC_TOP_PUBLIC_LOOP: case FC_TOP_PRIVATE_LOOP: case FC_TOP_PT_PT: if (pptr->port_state & FCP_STATE_NS_REG_FAILED) { fcp_retry_ns_registry(pptr, port_sid); } fcp_handle_devices(pptr, devlist, dev_cnt, link_count, map_tag, FCP_CAUSE_LINK_CHANGE); break; default: /* * We got here because we were provided with an unknown * topology. */ if (pptr->port_state & FCP_STATE_NS_REG_FAILED) { pptr->port_state &= ~FCP_STATE_NS_REG_FAILED; } pptr->port_tmp_cnt -= dev_cnt; fcp_log(CE_WARN, pptr->port_dip, "!unknown/unsupported topology (0x%x)", port_top); break; } FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "Notify ssd of the reset to reinstate the reservations"); scsi_hba_reset_notify_callback(&pptr->port_mutex, &pptr->port_reset_notify_listf); mutex_exit(&pptr->port_mutex); break; case FC_STATE_RESET: ASSERT(pptr->port_state & FCP_STATE_OFFLINE); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "RESET state, waiting for Offline/Online state_cb"); mutex_exit(&pptr->port_mutex); break; case FC_STATE_DEVICE_CHANGE: /* * We come here when an application has requested * Dynamic node creation/deletion in Fabric connectivity. */ if (pptr->port_state & (FCP_STATE_OFFLINE | FCP_STATE_INIT)) { /* * This case can happen when the FCTL is in the * process of giving us on online and the host on * the other side issues a PLOGI/PLOGO. Ideally * the state changes should be serialized unless * they are opposite (online-offline). * The transport will give us a final state change * so we can ignore this for the time being. */ pptr->port_state &= ~FCP_STATE_IN_CB_DEVC; mutex_exit(&pptr->port_mutex); break; } if (pptr->port_state & FCP_STATE_NS_REG_FAILED) { fcp_retry_ns_registry(pptr, port_sid); } /* * Extend the deadline under steady state conditions * to provide more time for the device-change-commands */ if (!pptr->port_ipkt_cnt) { pptr->port_deadline = fcp_watchdog_time + FCP_ICMD_DEADLINE; } /* * There is another race condition here, where if we were * in ONLINEING state and a devices in the map logs out, * fp will give another state change as DEVICE_CHANGE * and OLD. This will result in that target being offlined. * The pd_handle is freed. If from the first statec callback * we were going to fire a PLOGI/PRLI, the system will * panic in fc_ulp_transport with invalid pd_handle. * The fix is to check for the link_cnt before issuing * any command down. */ fcp_update_targets(pptr, devlist, dev_cnt, FCP_LUN_BUSY | FCP_LUN_MARK, FCP_CAUSE_TGT_CHANGE); link_count = pptr->port_link_cnt; fcp_handle_devices(pptr, devlist, dev_cnt, link_count, map_tag, FCP_CAUSE_TGT_CHANGE); pptr->port_state &= ~FCP_STATE_IN_CB_DEVC; mutex_exit(&pptr->port_mutex); break; case FC_STATE_TARGET_PORT_RESET: if (pptr->port_state & FCP_STATE_NS_REG_FAILED) { fcp_retry_ns_registry(pptr, port_sid); } /* Do nothing else */ mutex_exit(&pptr->port_mutex); break; default: fcp_log(CE_WARN, pptr->port_dip, "!Invalid state change=0x%x", port_state); mutex_exit(&pptr->port_mutex); break; } if (map_tag) { kmem_free(map_tag, map_len); } } /* * Function: fcp_handle_devices * * Description: This function updates the devices currently known by * walking the list provided by the caller. The list passed * by the caller is supposed to be the list of reachable * devices. * * Argument: *pptr Fcp port structure. * *devlist Pointer to the first entry of a table * containing the remote ports that can be * reached. * dev_cnt Number of entries pointed by devlist. * link_cnt Link state count. * *map_tag Array of fcp_map_tag_t structures. * cause What caused this function to be called. * * Return Value: None * * Notes: The pptr->port_mutex must be held. */ static void fcp_handle_devices(struct fcp_port *pptr, fc_portmap_t devlist[], uint32_t dev_cnt, int link_cnt, fcp_map_tag_t *map_tag, int cause) { int i; int check_finish_init = 0; fc_portmap_t *map_entry; struct fcp_tgt *ptgt = NULL; FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_handle_devices: called for %d dev(s)", dev_cnt); if (dev_cnt) { ASSERT(map_tag != NULL); } /* * The following code goes through the list of remote ports that are * accessible through this (pptr) local port (The list walked is the * one provided by the caller which is the list of the remote ports * currently reachable). It checks if any of them was already * known by looking for the corresponding target structure based on * the world wide name. If a target is part of the list it is tagged * (ptgt->tgt_aux_state = FCP_TGT_TAGGED). * * Old comment * ----------- * Before we drop port mutex; we MUST get the tags updated; This * two step process is somewhat slow, but more reliable. */ for (i = 0; (i < dev_cnt) && (pptr->port_link_cnt == link_cnt); i++) { map_entry = &(devlist[i]); /* * get ptr to this map entry in our port's * list (if any) */ ptgt = fcp_lookup_target(pptr, (uchar_t *)&(map_entry->map_pwwn)); if (ptgt) { map_tag[i] = ptgt->tgt_change_cnt; if (cause == FCP_CAUSE_LINK_CHANGE) { ptgt->tgt_aux_state = FCP_TGT_TAGGED; } } } /* * At this point we know which devices of the new list were already * known (The field tgt_aux_state of the target structure has been * set to FCP_TGT_TAGGED). * * The following code goes through the list of targets currently known * by the local port (the list is actually a hashing table). If a * target is found and is not tagged, it means the target cannot * be reached anymore through the local port (pptr). It is offlined. * The offlining only occurs if the cause is FCP_CAUSE_LINK_CHANGE. */ for (i = 0; i < FCP_NUM_HASH; i++) { for (ptgt = pptr->port_tgt_hash_table[i]; ptgt != NULL; ptgt = ptgt->tgt_next) { mutex_enter(&ptgt->tgt_mutex); if ((ptgt->tgt_aux_state != FCP_TGT_TAGGED) && (cause == FCP_CAUSE_LINK_CHANGE) && !(ptgt->tgt_state & FCP_TGT_OFFLINE)) { fcp_offline_target_now(pptr, ptgt, link_cnt, ptgt->tgt_change_cnt, 0); } mutex_exit(&ptgt->tgt_mutex); } } /* * At this point, the devices that were known but cannot be reached * anymore, have most likely been offlined. * * The following section of code seems to go through the list of * remote ports that can now be reached. For every single one it * checks if it is already known or if it is a new port. */ for (i = 0; (i < dev_cnt) && (pptr->port_link_cnt == link_cnt); i++) { if (check_finish_init) { ASSERT(i > 0); (void) fcp_call_finish_init_held(pptr, ptgt, link_cnt, map_tag[i - 1], cause); check_finish_init = 0; } /* get a pointer to this map entry */ map_entry = &(devlist[i]); /* * Check for the duplicate map entry flag. If we have marked * this entry as a duplicate we skip it since the correct * (perhaps even same) state change will be encountered * later in the list. */ if (map_entry->map_flags & PORT_DEVICE_DUPLICATE_MAP_ENTRY) { continue; } /* get ptr to this map entry in our port's list (if any) */ ptgt = fcp_lookup_target(pptr, (uchar_t *)&(map_entry->map_pwwn)); if (ptgt) { /* * This device was already known. The field * tgt_aux_state is reset (was probably set to * FCP_TGT_TAGGED previously in this routine). */ ptgt->tgt_aux_state = 0; FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "handle_devices: map did/state/type/flags = " "0x%x/0x%x/0x%x/0x%x, tgt_d_id=0x%x, " "tgt_state=%d", map_entry->map_did.port_id, map_entry->map_state, map_entry->map_type, map_entry->map_flags, ptgt->tgt_d_id, ptgt->tgt_state); } if (map_entry->map_type == PORT_DEVICE_OLD || map_entry->map_type == PORT_DEVICE_NEW || map_entry->map_type == PORT_DEVICE_CHANGED) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "map_type=%x, did = %x", map_entry->map_type, map_entry->map_did.port_id); } switch (map_entry->map_type) { case PORT_DEVICE_NOCHANGE: case PORT_DEVICE_USER_CREATE: case PORT_DEVICE_USER_LOGIN: case PORT_DEVICE_NEW: FCP_TGT_TRACE(ptgt, map_tag[i], FCP_TGT_TRACE_1); if (fcp_handle_mapflags(pptr, ptgt, map_entry, link_cnt, (ptgt) ? map_tag[i] : 0, cause) == TRUE) { FCP_TGT_TRACE(ptgt, map_tag[i], FCP_TGT_TRACE_2); check_finish_init++; } break; case PORT_DEVICE_OLD: if (ptgt != NULL) { FCP_TGT_TRACE(ptgt, map_tag[i], FCP_TGT_TRACE_3); mutex_enter(&ptgt->tgt_mutex); if (!(ptgt->tgt_state & FCP_TGT_OFFLINE)) { /* * Must do an in-line wait for I/Os * to get drained */ mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); mutex_enter(&ptgt->tgt_mutex); while (ptgt->tgt_ipkt_cnt || fcp_outstanding_lun_cmds(ptgt) == FC_SUCCESS) { mutex_exit(&ptgt->tgt_mutex); delay(drv_usectohz(1000000)); mutex_enter(&ptgt->tgt_mutex); } mutex_exit(&ptgt->tgt_mutex); mutex_enter(&pptr->port_mutex); mutex_enter(&ptgt->tgt_mutex); (void) fcp_offline_target(pptr, ptgt, link_cnt, map_tag[i], 0, 0); } mutex_exit(&ptgt->tgt_mutex); } check_finish_init++; break; case PORT_DEVICE_USER_DELETE: case PORT_DEVICE_USER_LOGOUT: if (ptgt != NULL) { FCP_TGT_TRACE(ptgt, map_tag[i], FCP_TGT_TRACE_4); mutex_enter(&ptgt->tgt_mutex); if (!(ptgt->tgt_state & FCP_TGT_OFFLINE)) { (void) fcp_offline_target(pptr, ptgt, link_cnt, map_tag[i], 1, 0); } mutex_exit(&ptgt->tgt_mutex); } check_finish_init++; break; case PORT_DEVICE_CHANGED: if (ptgt != NULL) { FCP_TGT_TRACE(ptgt, map_tag[i], FCP_TGT_TRACE_5); if (fcp_device_changed(pptr, ptgt, map_entry, link_cnt, map_tag[i], cause) == TRUE) { check_finish_init++; } } else { if (fcp_handle_mapflags(pptr, ptgt, map_entry, link_cnt, 0, cause) == TRUE) { check_finish_init++; } } break; default: fcp_log(CE_WARN, pptr->port_dip, "!Invalid map_type=0x%x", map_entry->map_type); check_finish_init++; break; } } if (check_finish_init && pptr->port_link_cnt == link_cnt) { ASSERT(i > 0); (void) fcp_call_finish_init_held(pptr, ptgt, link_cnt, map_tag[i-1], cause); } else if (dev_cnt == 0 && pptr->port_link_cnt == link_cnt) { fcp_offline_all(pptr, link_cnt, cause); } } /* * Function: fcp_handle_mapflags * * Description: This function creates a target structure if the ptgt passed * is NULL. It also kicks off the PLOGI if we are not logged * into the target yet or the PRLI if we are logged into the * target already. The rest of the treatment is done in the * callbacks of the PLOGI or PRLI. * * Argument: *pptr FCP Port structure. * *ptgt Target structure. * *map_entry Array of fc_portmap_t structures. * link_cnt Link state count. * tgt_cnt Target state count. * cause What caused this function to be called. * * Return Value: TRUE Failed * FALSE Succeeded * * Notes: pptr->port_mutex must be owned. */ static int fcp_handle_mapflags(struct fcp_port *pptr, struct fcp_tgt *ptgt, fc_portmap_t *map_entry, int link_cnt, int tgt_cnt, int cause) { int lcount; int tcount; int ret = TRUE; int alloc; struct fcp_ipkt *icmd; struct fcp_lun *pseq_lun = NULL; uchar_t opcode; int valid_ptgt_was_passed = FALSE; ASSERT(mutex_owned(&pptr->port_mutex)); /* * This case is possible where the FCTL has come up and done discovery * before FCP was loaded and attached. FCTL would have discovered the * devices and later the ULP came online. In this case ULP's would get * PORT_DEVICE_NOCHANGE but target would be NULL. */ if (ptgt == NULL) { /* don't already have a target */ mutex_exit(&pptr->port_mutex); ptgt = fcp_alloc_tgt(pptr, map_entry, link_cnt); mutex_enter(&pptr->port_mutex); if (ptgt == NULL) { fcp_log(CE_WARN, pptr->port_dip, "!FC target allocation failed"); return (ret); } mutex_enter(&ptgt->tgt_mutex); ptgt->tgt_statec_cause = cause; ptgt->tgt_tmp_cnt = 1; mutex_exit(&ptgt->tgt_mutex); } else { valid_ptgt_was_passed = TRUE; } /* * Copy in the target parameters */ mutex_enter(&ptgt->tgt_mutex); ptgt->tgt_d_id = map_entry->map_did.port_id; ptgt->tgt_hard_addr = map_entry->map_hard_addr.hard_addr; ptgt->tgt_pd_handle = map_entry->map_pd; ptgt->tgt_fca_dev = NULL; /* Copy port and node WWNs */ bcopy(&map_entry->map_nwwn, &ptgt->tgt_node_wwn.raw_wwn[0], FC_WWN_SIZE); bcopy(&map_entry->map_pwwn, &ptgt->tgt_port_wwn.raw_wwn[0], FC_WWN_SIZE); if (!(map_entry->map_flags & PORT_DEVICE_NO_SKIP_DEVICE_DISCOVERY) && (map_entry->map_type == PORT_DEVICE_NOCHANGE) && (map_entry->map_state == PORT_DEVICE_LOGGED_IN) && valid_ptgt_was_passed) { /* * determine if there are any tape LUNs on this target */ for (pseq_lun = ptgt->tgt_lun; pseq_lun != NULL; pseq_lun = pseq_lun->lun_next) { if ((pseq_lun->lun_type == DTYPE_SEQUENTIAL) && !(pseq_lun->lun_state & FCP_LUN_OFFLINE)) { fcp_update_tgt_state(ptgt, FCP_RESET, FCP_LUN_MARK); mutex_exit(&ptgt->tgt_mutex); return (ret); } } } /* * If ptgt was NULL when this function was entered, then tgt_node_state * was never specifically initialized but zeroed out which means * FCP_TGT_NODE_NONE. */ switch (ptgt->tgt_node_state) { case FCP_TGT_NODE_NONE: case FCP_TGT_NODE_ON_DEMAND: if (FC_TOP_EXTERNAL(pptr->port_topology) && !fcp_enable_auto_configuration && map_entry->map_type != PORT_DEVICE_USER_CREATE) { ptgt->tgt_node_state = FCP_TGT_NODE_ON_DEMAND; } else if (FC_TOP_EXTERNAL(pptr->port_topology) && fcp_enable_auto_configuration && (ptgt->tgt_manual_config_only == 1) && map_entry->map_type != PORT_DEVICE_USER_CREATE) { /* * If auto configuration is set and * the tgt_manual_config_only flag is set then * we only want the user to be able to change * the state through create_on_demand. */ ptgt->tgt_node_state = FCP_TGT_NODE_ON_DEMAND; } else { ptgt->tgt_node_state = FCP_TGT_NODE_NONE; } break; case FCP_TGT_NODE_PRESENT: break; } /* * If we are booting from a fabric device, make sure we * mark the node state appropriately for this target to be * enumerated */ if (FC_TOP_EXTERNAL(pptr->port_topology) && pptr->port_boot_wwn[0]) { if (bcmp((caddr_t)pptr->port_boot_wwn, (caddr_t)&ptgt->tgt_port_wwn.raw_wwn[0], sizeof (ptgt->tgt_port_wwn)) == 0) { ptgt->tgt_node_state = FCP_TGT_NODE_NONE; } } mutex_exit(&ptgt->tgt_mutex); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "map_pd=%p, map_type=%x, did = %x, ulp_rscn_count=0x%x", map_entry->map_pd, map_entry->map_type, map_entry->map_did.port_id, map_entry->map_rscn_info.ulp_rscn_count); mutex_enter(&ptgt->tgt_mutex); /* * Reset target OFFLINE state and mark the target BUSY */ ptgt->tgt_state &= ~FCP_TGT_OFFLINE; ptgt->tgt_state |= (FCP_TGT_BUSY | FCP_TGT_MARK); tcount = tgt_cnt ? tgt_cnt : ptgt->tgt_change_cnt; lcount = link_cnt; mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); /* * if we are already logged in, then we do a PRLI, else * we do a PLOGI first (to get logged in) * * We will not check if we are the PLOGI initiator */ opcode = (map_entry->map_state == PORT_DEVICE_LOGGED_IN && map_entry->map_pd != NULL) ? LA_ELS_PRLI : LA_ELS_PLOGI; alloc = FCP_MAX(sizeof (la_els_logi_t), sizeof (la_els_prli_t)); icmd = fcp_icmd_alloc(pptr, ptgt, alloc, alloc, 0, 0, lcount, tcount, cause, map_entry->map_rscn_info.ulp_rscn_count); if (icmd == NULL) { FCP_TGT_TRACE(ptgt, tgt_cnt, FCP_TGT_TRACE_29); /* * We've exited port_mutex before calling fcp_icmd_alloc, * we need to make sure we reacquire it before returning. */ mutex_enter(&pptr->port_mutex); return (FALSE); } /* TRUE is only returned while target is intended skipped */ ret = FALSE; /* discover info about this target */ if ((fcp_send_els(pptr, ptgt, icmd, opcode, lcount, tcount, cause)) == DDI_SUCCESS) { FCP_TGT_TRACE(ptgt, tgt_cnt, FCP_TGT_TRACE_9); } else { fcp_icmd_free(pptr, icmd); ret = TRUE; } mutex_enter(&pptr->port_mutex); return (ret); } /* * Function: fcp_send_els * * Description: Sends an ELS to the target specified by the caller. Supports * PLOGI and PRLI. * * Argument: *pptr Fcp port. * *ptgt Target to send the ELS to. * *icmd Internal packet * opcode ELS opcode * lcount Link state change counter * tcount Target state change counter * cause What caused the call * * Return Value: DDI_SUCCESS * Others */ static int fcp_send_els(struct fcp_port *pptr, struct fcp_tgt *ptgt, struct fcp_ipkt *icmd, uchar_t opcode, int lcount, int tcount, int cause) { fc_packet_t *fpkt; fc_frame_hdr_t *hp; int internal = 0; int alloc; int cmd_len; int resp_len; int res = DDI_FAILURE; /* default result */ int rval = DDI_FAILURE; ASSERT(opcode == LA_ELS_PLOGI || opcode == LA_ELS_PRLI); ASSERT(ptgt->tgt_port == pptr); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "fcp_send_els: d_id=0x%x ELS 0x%x (%s)", ptgt->tgt_d_id, opcode, (opcode == LA_ELS_PLOGI) ? "PLOGI" : "PRLI"); if (opcode == LA_ELS_PLOGI) { cmd_len = sizeof (la_els_logi_t); resp_len = sizeof (la_els_logi_t); } else { ASSERT(opcode == LA_ELS_PRLI); cmd_len = sizeof (la_els_prli_t); resp_len = sizeof (la_els_prli_t); } if (icmd == NULL) { alloc = FCP_MAX(sizeof (la_els_logi_t), sizeof (la_els_prli_t)); icmd = fcp_icmd_alloc(pptr, ptgt, alloc, alloc, 0, 0, lcount, tcount, cause, FC_INVALID_RSCN_COUNT); if (icmd == NULL) { FCP_TGT_TRACE(ptgt, tcount, FCP_TGT_TRACE_10); return (res); } internal++; } fpkt = icmd->ipkt_fpkt; fpkt->pkt_cmdlen = cmd_len; fpkt->pkt_rsplen = resp_len; fpkt->pkt_datalen = 0; icmd->ipkt_retries = 0; /* fill in fpkt info */ fpkt->pkt_tran_flags = FC_TRAN_CLASS3 | FC_TRAN_INTR; fpkt->pkt_tran_type = FC_PKT_EXCHANGE; fpkt->pkt_timeout = FCP_ELS_TIMEOUT; /* get ptr to frame hdr in fpkt */ hp = &fpkt->pkt_cmd_fhdr; /* * fill in frame hdr */ hp->r_ctl = R_CTL_ELS_REQ; hp->s_id = pptr->port_id; /* source ID */ hp->d_id = ptgt->tgt_d_id; /* dest ID */ hp->type = FC_TYPE_EXTENDED_LS; hp->f_ctl = F_CTL_SEQ_INITIATIVE | F_CTL_FIRST_SEQ; hp->seq_id = 0; hp->rsvd = 0; hp->df_ctl = 0; hp->seq_cnt = 0; hp->ox_id = 0xffff; /* i.e. none */ hp->rx_id = 0xffff; /* i.e. none */ hp->ro = 0; /* * at this point we have a filled in cmd pkt * * fill in the respective info, then use the transport to send * the packet * * for a PLOGI call fc_ulp_login(), and * for a PRLI call fc_ulp_issue_els() */ switch (opcode) { case LA_ELS_PLOGI: { struct la_els_logi logi; bzero(&logi, sizeof (struct la_els_logi)); hp = &fpkt->pkt_cmd_fhdr; hp->r_ctl = R_CTL_ELS_REQ; logi.ls_code.ls_code = LA_ELS_PLOGI; logi.ls_code.mbz = 0; FCP_CP_OUT((uint8_t *)&logi, fpkt->pkt_cmd, fpkt->pkt_cmd_acc, sizeof (struct la_els_logi)); icmd->ipkt_opcode = LA_ELS_PLOGI; mutex_enter(&pptr->port_mutex); if (!FCP_TGT_STATE_CHANGED(ptgt, icmd)) { mutex_exit(&pptr->port_mutex); rval = fc_ulp_login(pptr->port_fp_handle, &fpkt, 1); if (rval == FC_SUCCESS) { res = DDI_SUCCESS; break; } FCP_TGT_TRACE(ptgt, tcount, FCP_TGT_TRACE_11); res = fcp_handle_ipkt_errors(pptr, ptgt, icmd, rval, "PLOGI"); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "fcp_send_els1: state change occured" " for D_ID=0x%x", ptgt->tgt_d_id); mutex_exit(&pptr->port_mutex); FCP_TGT_TRACE(ptgt, tcount, FCP_TGT_TRACE_12); } break; } case LA_ELS_PRLI: { struct la_els_prli prli; struct fcp_prli *fprli; bzero(&prli, sizeof (struct la_els_prli)); hp = &fpkt->pkt_cmd_fhdr; hp->r_ctl = R_CTL_ELS_REQ; /* fill in PRLI cmd ELS fields */ prli.ls_code = LA_ELS_PRLI; prli.page_length = 0x10; /* huh? */ prli.payload_length = sizeof (struct la_els_prli); icmd->ipkt_opcode = LA_ELS_PRLI; /* get ptr to PRLI service params */ fprli = (struct fcp_prli *)prli.service_params; /* fill in service params */ fprli->type = 0x08; fprli->resvd1 = 0; fprli->orig_process_assoc_valid = 0; fprli->resp_process_assoc_valid = 0; fprli->establish_image_pair = 1; fprli->resvd2 = 0; fprli->resvd3 = 0; fprli->obsolete_1 = 0; fprli->obsolete_2 = 0; fprli->data_overlay_allowed = 0; fprli->initiator_fn = 1; fprli->confirmed_compl_allowed = 1; if (fc_ulp_is_name_present("ltct") == FC_SUCCESS) { fprli->target_fn = 1; } else { fprli->target_fn = 0; } fprli->retry = 1; fprli->read_xfer_rdy_disabled = 1; fprli->write_xfer_rdy_disabled = 0; FCP_CP_OUT((uint8_t *)&prli, fpkt->pkt_cmd, fpkt->pkt_cmd_acc, sizeof (struct la_els_prli)); /* issue the PRLI request */ mutex_enter(&pptr->port_mutex); if (!FCP_TGT_STATE_CHANGED(ptgt, icmd)) { mutex_exit(&pptr->port_mutex); rval = fc_ulp_issue_els(pptr->port_fp_handle, fpkt); if (rval == FC_SUCCESS) { res = DDI_SUCCESS; break; } FCP_TGT_TRACE(ptgt, tcount, FCP_TGT_TRACE_13); res = fcp_handle_ipkt_errors(pptr, ptgt, icmd, rval, "PRLI"); } else { mutex_exit(&pptr->port_mutex); FCP_TGT_TRACE(ptgt, tcount, FCP_TGT_TRACE_14); } break; } default: fcp_log(CE_WARN, NULL, "!invalid ELS opcode=0x%x", opcode); break; } FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "fcp_send_els: returning %d", res); if (res != DDI_SUCCESS) { if (internal) { fcp_icmd_free(pptr, icmd); } } return (res); } /* * called internally update the state of all of the tgts and each LUN * for this port (i.e. each target known to be attached to this port) * if they are not already offline * * must be called with the port mutex owned * * acquires and releases the target mutexes for each target attached * to this port */ void fcp_update_state(struct fcp_port *pptr, uint32_t state, int cause) { int i; struct fcp_tgt *ptgt; ASSERT(mutex_owned(&pptr->port_mutex)); for (i = 0; i < FCP_NUM_HASH; i++) { for (ptgt = pptr->port_tgt_hash_table[i]; ptgt != NULL; ptgt = ptgt->tgt_next) { mutex_enter(&ptgt->tgt_mutex); fcp_update_tgt_state(ptgt, FCP_SET, state); ptgt->tgt_change_cnt++; ptgt->tgt_statec_cause = cause; ptgt->tgt_tmp_cnt = 1; ptgt->tgt_done = 0; mutex_exit(&ptgt->tgt_mutex); } } } static void fcp_offline_all(struct fcp_port *pptr, int lcount, int cause) { int i; int ndevs; struct fcp_tgt *ptgt; ASSERT(mutex_owned(&pptr->port_mutex)); for (ndevs = 0, i = 0; i < FCP_NUM_HASH; i++) { for (ptgt = pptr->port_tgt_hash_table[i]; ptgt != NULL; ptgt = ptgt->tgt_next) { ndevs++; } } if (ndevs == 0) { return; } pptr->port_tmp_cnt = ndevs; for (i = 0; i < FCP_NUM_HASH; i++) { for (ptgt = pptr->port_tgt_hash_table[i]; ptgt != NULL; ptgt = ptgt->tgt_next) { (void) fcp_call_finish_init_held(pptr, ptgt, lcount, ptgt->tgt_change_cnt, cause); } } } /* * Function: fcp_update_tgt_state * * Description: This function updates the field tgt_state of a target. That * field is a bitmap and which bit can be set or reset * individually. The action applied to the target state is also * applied to all the LUNs belonging to the target (provided the * LUN is not offline). A side effect of applying the state * modification to the target and the LUNs is the field tgt_trace * of the target and lun_trace of the LUNs is set to zero. * * * Argument: *ptgt Target structure. * flag Flag indication what action to apply (set/reset). * state State bits to update. * * Return Value: None * * Context: Interrupt, Kernel or User context. * The mutex of the target (ptgt->tgt_mutex) must be owned when * calling this function. */ void fcp_update_tgt_state(struct fcp_tgt *ptgt, int flag, uint32_t state) { struct fcp_lun *plun; ASSERT(mutex_owned(&ptgt->tgt_mutex)); if (!(ptgt->tgt_state & FCP_TGT_OFFLINE)) { /* The target is not offline. */ if (flag == FCP_SET) { ptgt->tgt_state |= state; ptgt->tgt_trace = 0; } else { ptgt->tgt_state &= ~state; } for (plun = ptgt->tgt_lun; plun != NULL; plun = plun->lun_next) { if (!(plun->lun_state & FCP_LUN_OFFLINE)) { /* The LUN is not offline. */ if (flag == FCP_SET) { plun->lun_state |= state; plun->lun_trace = 0; } else { plun->lun_state &= ~state; } } } } } /* * Function: fcp_update_tgt_state * * Description: This function updates the field lun_state of a LUN. That * field is a bitmap and which bit can be set or reset * individually. * * Argument: *plun LUN structure. * flag Flag indication what action to apply (set/reset). * state State bits to update. * * Return Value: None * * Context: Interrupt, Kernel or User context. * The mutex of the target (ptgt->tgt_mutex) must be owned when * calling this function. */ void fcp_update_lun_state(struct fcp_lun *plun, int flag, uint32_t state) { struct fcp_tgt *ptgt = plun->lun_tgt; ASSERT(mutex_owned(&ptgt->tgt_mutex)); if (!(plun->lun_state & FCP_TGT_OFFLINE)) { if (flag == FCP_SET) { plun->lun_state |= state; } else { plun->lun_state &= ~state; } } } /* * Function: fcp_get_port * * Description: This function returns the fcp_port structure from the opaque * handle passed by the caller. That opaque handle is the handle * used by fp/fctl to identify a particular local port. That * handle has been stored in the corresponding fcp_port * structure. This function is going to walk the global list of * fcp_port structures till one has a port_fp_handle that matches * the handle passed by the caller. This function enters the * mutex fcp_global_mutex while walking the global list and then * releases it. * * Argument: port_handle Opaque handle that fp/fctl uses to identify a * particular port. * * Return Value: NULL Not found. * Not NULL Pointer to the fcp_port structure. * * Context: Interrupt, Kernel or User context. */ static struct fcp_port * fcp_get_port(opaque_t port_handle) { struct fcp_port *pptr; ASSERT(port_handle != NULL); mutex_enter(&fcp_global_mutex); for (pptr = fcp_port_head; pptr != NULL; pptr = pptr->port_next) { if (pptr->port_fp_handle == port_handle) { break; } } mutex_exit(&fcp_global_mutex); return (pptr); } static void fcp_unsol_callback(fc_packet_t *fpkt) { struct fcp_ipkt *icmd = (struct fcp_ipkt *)fpkt->pkt_ulp_private; struct fcp_port *pptr = icmd->ipkt_port; if (fpkt->pkt_state != FC_PKT_SUCCESS) { caddr_t state, reason, action, expln; (void) fc_ulp_pkt_error(fpkt, &state, &reason, &action, &expln); fcp_log(CE_WARN, pptr->port_dip, "!couldn't post response to unsolicited request: " " state=%s reason=%s rx_id=%x ox_id=%x", state, reason, fpkt->pkt_cmd_fhdr.ox_id, fpkt->pkt_cmd_fhdr.rx_id); } fcp_icmd_free(pptr, icmd); } /* * Perform general purpose preparation of a response to an unsolicited request */ static void fcp_unsol_resp_init(fc_packet_t *pkt, fc_unsol_buf_t *buf, uchar_t r_ctl, uchar_t type) { pkt->pkt_cmd_fhdr.r_ctl = r_ctl; pkt->pkt_cmd_fhdr.d_id = buf->ub_frame.s_id; pkt->pkt_cmd_fhdr.s_id = buf->ub_frame.d_id; pkt->pkt_cmd_fhdr.type = type; pkt->pkt_cmd_fhdr.f_ctl = F_CTL_LAST_SEQ | F_CTL_XCHG_CONTEXT; pkt->pkt_cmd_fhdr.seq_id = buf->ub_frame.seq_id; pkt->pkt_cmd_fhdr.df_ctl = buf->ub_frame.df_ctl; pkt->pkt_cmd_fhdr.seq_cnt = buf->ub_frame.seq_cnt; pkt->pkt_cmd_fhdr.ox_id = buf->ub_frame.ox_id; pkt->pkt_cmd_fhdr.rx_id = buf->ub_frame.rx_id; pkt->pkt_cmd_fhdr.ro = 0; pkt->pkt_cmd_fhdr.rsvd = 0; pkt->pkt_comp = fcp_unsol_callback; pkt->pkt_pd = NULL; } /*ARGSUSED*/ static int fcp_unsol_prli(struct fcp_port *pptr, fc_unsol_buf_t *buf) { fc_packet_t *fpkt; struct la_els_prli prli; struct fcp_prli *fprli; struct fcp_ipkt *icmd; struct la_els_prli *from; struct fcp_prli *orig; struct fcp_tgt *ptgt; int tcount = 0; int lcount; from = (struct la_els_prli *)buf->ub_buffer; orig = (struct fcp_prli *)from->service_params; if ((ptgt = fcp_get_target_by_did(pptr, buf->ub_frame.s_id)) != NULL) { mutex_enter(&ptgt->tgt_mutex); tcount = ptgt->tgt_change_cnt; mutex_exit(&ptgt->tgt_mutex); } mutex_enter(&pptr->port_mutex); lcount = pptr->port_link_cnt; mutex_exit(&pptr->port_mutex); if ((icmd = fcp_icmd_alloc(pptr, ptgt, sizeof (la_els_prli_t), sizeof (la_els_prli_t), 0, 0, lcount, tcount, 0, FC_INVALID_RSCN_COUNT)) == NULL) { return (FC_FAILURE); } fpkt = icmd->ipkt_fpkt; fpkt->pkt_tran_flags = FC_TRAN_CLASS3 | FC_TRAN_INTR; fpkt->pkt_tran_type = FC_PKT_OUTBOUND; fpkt->pkt_timeout = FCP_ELS_TIMEOUT; fpkt->pkt_cmdlen = sizeof (la_els_prli_t); fpkt->pkt_rsplen = 0; fpkt->pkt_datalen = 0; icmd->ipkt_opcode = LA_ELS_PRLI; bzero(&prli, sizeof (struct la_els_prli)); fprli = (struct fcp_prli *)prli.service_params; prli.ls_code = LA_ELS_ACC; prli.page_length = 0x10; prli.payload_length = sizeof (struct la_els_prli); /* fill in service params */ fprli->type = 0x08; fprli->resvd1 = 0; fprli->orig_process_assoc_valid = orig->orig_process_assoc_valid; fprli->orig_process_associator = orig->orig_process_associator; fprli->resp_process_assoc_valid = 0; fprli->establish_image_pair = 1; fprli->resvd2 = 0; fprli->resvd3 = 0; fprli->obsolete_1 = 0; fprli->obsolete_2 = 0; fprli->data_overlay_allowed = 0; fprli->initiator_fn = 1; fprli->confirmed_compl_allowed = 1; if (fc_ulp_is_name_present("ltct") == FC_SUCCESS) { fprli->target_fn = 1; } else { fprli->target_fn = 0; } fprli->retry = 1; fprli->read_xfer_rdy_disabled = 1; fprli->write_xfer_rdy_disabled = 0; /* save the unsol prli payload first */ FCP_CP_OUT((uint8_t *)from, fpkt->pkt_resp, fpkt->pkt_resp_acc, sizeof (struct la_els_prli)); FCP_CP_OUT((uint8_t *)&prli, fpkt->pkt_cmd, fpkt->pkt_cmd_acc, sizeof (struct la_els_prli)); fcp_unsol_resp_init(fpkt, buf, R_CTL_ELS_RSP, FC_TYPE_EXTENDED_LS); mutex_enter(&pptr->port_mutex); if (!FCP_LINK_STATE_CHANGED(pptr, icmd)) { int rval; mutex_exit(&pptr->port_mutex); if ((rval = fc_ulp_issue_els(pptr->port_fp_handle, fpkt)) != FC_SUCCESS) { if (rval == FC_STATEC_BUSY || rval == FC_OFFLINE) { fcp_queue_ipkt(pptr, fpkt); return (FC_SUCCESS); } /* Let it timeout */ fcp_icmd_free(pptr, icmd); return (FC_FAILURE); } } else { mutex_exit(&pptr->port_mutex); fcp_icmd_free(pptr, icmd); return (FC_FAILURE); } (void) fc_ulp_ubrelease(pptr->port_fp_handle, 1, &buf->ub_token); return (FC_SUCCESS); } /* * Function: fcp_icmd_alloc * * Description: This function allocated a fcp_ipkt structure. The pkt_comp * field is initialized to fcp_icmd_callback. Sometimes it is * modified by the caller (such as fcp_send_scsi). The * structure is also tied to the state of the line and of the * target at a particular time. That link is established by * setting the fields ipkt_link_cnt and ipkt_change_cnt to lcount * and tcount which came respectively from pptr->link_cnt and * ptgt->tgt_change_cnt. * * Argument: *pptr Fcp port. * *ptgt Target (destination of the command). * cmd_len Length of the command. * resp_len Length of the expected response. * data_len Length of the data. * nodma Indicates weither the command and response. * will be transfer through DMA or not. * lcount Link state change counter. * tcount Target state change counter. * cause Reason that lead to this call. * * Return Value: NULL Failed. * Not NULL Internal packet address. */ static struct fcp_ipkt * fcp_icmd_alloc(struct fcp_port *pptr, struct fcp_tgt *ptgt, int cmd_len, int resp_len, int data_len, int nodma, int lcount, int tcount, int cause, uint32_t rscn_count) { int dma_setup = 0; fc_packet_t *fpkt; struct fcp_ipkt *icmd = NULL; icmd = kmem_zalloc(sizeof (struct fcp_ipkt) + pptr->port_dmacookie_sz + pptr->port_priv_pkt_len, KM_NOSLEEP); if (icmd == NULL) { fcp_log(CE_WARN, pptr->port_dip, "!internal packet allocation failed"); return (NULL); } /* * initialize the allocated packet */ icmd->ipkt_nodma = nodma; icmd->ipkt_next = icmd->ipkt_prev = NULL; icmd->ipkt_lun = NULL; icmd->ipkt_link_cnt = lcount; icmd->ipkt_change_cnt = tcount; icmd->ipkt_cause = cause; mutex_enter(&pptr->port_mutex); icmd->ipkt_port = pptr; mutex_exit(&pptr->port_mutex); /* keep track of amt of data to be sent in pkt */ icmd->ipkt_cmdlen = cmd_len; icmd->ipkt_resplen = resp_len; icmd->ipkt_datalen = data_len; /* set up pkt's ptr to the fc_packet_t struct, just after the ipkt */ icmd->ipkt_fpkt = (fc_packet_t *)(&icmd->ipkt_fc_packet); /* set pkt's private ptr to point to cmd pkt */ icmd->ipkt_fpkt->pkt_ulp_private = (opaque_t)icmd; /* set FCA private ptr to memory just beyond */ icmd->ipkt_fpkt->pkt_fca_private = (opaque_t) ((char *)icmd + sizeof (struct fcp_ipkt) + pptr->port_dmacookie_sz); /* get ptr to fpkt substruct and fill it in */ fpkt = icmd->ipkt_fpkt; fpkt->pkt_data_cookie = (ddi_dma_cookie_t *)((caddr_t)icmd + sizeof (struct fcp_ipkt)); if (ptgt != NULL) { icmd->ipkt_tgt = ptgt; fpkt->pkt_fca_device = ptgt->tgt_fca_dev; } fpkt->pkt_comp = fcp_icmd_callback; fpkt->pkt_tran_flags = (FC_TRAN_CLASS3 | FC_TRAN_INTR); fpkt->pkt_cmdlen = cmd_len; fpkt->pkt_rsplen = resp_len; fpkt->pkt_datalen = data_len; /* * The pkt_ulp_rscn_infop (aka pkt_ulp_rsvd1) field is used to pass the * rscn_count as fcp knows down to the transport. If a valid count was * passed into this function, we allocate memory to actually pass down * this info. * * BTW, if the kmem_zalloc fails, we won't try too hard. This will * basically mean that fcp will not be able to help transport * distinguish if a new RSCN has come after fcp was last informed about * it. In such cases, it might lead to the problem mentioned in CR/bug # * 5068068 where the device might end up going offline in case of RSCN * storms. */ fpkt->pkt_ulp_rscn_infop = NULL; if (rscn_count != FC_INVALID_RSCN_COUNT) { fpkt->pkt_ulp_rscn_infop = kmem_zalloc( sizeof (fc_ulp_rscn_info_t), KM_NOSLEEP); if (fpkt->pkt_ulp_rscn_infop == NULL) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_6, 0, "Failed to alloc memory to pass rscn info"); } } if (fpkt->pkt_ulp_rscn_infop != NULL) { fc_ulp_rscn_info_t *rscnp; rscnp = (fc_ulp_rscn_info_t *)fpkt->pkt_ulp_rscn_infop; rscnp->ulp_rscn_count = rscn_count; } if (fcp_alloc_dma(pptr, icmd, nodma, KM_NOSLEEP) != FC_SUCCESS) { goto fail; } dma_setup++; /* * Must hold target mutex across setting of pkt_pd and call to * fc_ulp_init_packet to ensure the handle to the target doesn't go * away while we're not looking. */ if (ptgt != NULL) { mutex_enter(&ptgt->tgt_mutex); fpkt->pkt_pd = ptgt->tgt_pd_handle; /* ask transport to do its initialization on this pkt */ if (fc_ulp_init_packet(pptr->port_fp_handle, fpkt, KM_NOSLEEP) != FC_SUCCESS) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_6, 0, "fc_ulp_init_packet failed"); mutex_exit(&ptgt->tgt_mutex); goto fail; } mutex_exit(&ptgt->tgt_mutex); } else { if (fc_ulp_init_packet(pptr->port_fp_handle, fpkt, KM_NOSLEEP) != FC_SUCCESS) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_6, 0, "fc_ulp_init_packet failed"); goto fail; } } mutex_enter(&pptr->port_mutex); if (pptr->port_state & (FCP_STATE_DETACHING | FCP_STATE_SUSPENDED | FCP_STATE_POWER_DOWN)) { int rval; mutex_exit(&pptr->port_mutex); rval = fc_ulp_uninit_packet(pptr->port_fp_handle, fpkt); ASSERT(rval == FC_SUCCESS); goto fail; } if (ptgt != NULL) { mutex_enter(&ptgt->tgt_mutex); ptgt->tgt_ipkt_cnt++; mutex_exit(&ptgt->tgt_mutex); } pptr->port_ipkt_cnt++; mutex_exit(&pptr->port_mutex); return (icmd); fail: if (fpkt->pkt_ulp_rscn_infop != NULL) { kmem_free(fpkt->pkt_ulp_rscn_infop, sizeof (fc_ulp_rscn_info_t)); fpkt->pkt_ulp_rscn_infop = NULL; } if (dma_setup) { fcp_free_dma(pptr, icmd); } kmem_free(icmd, sizeof (struct fcp_ipkt) + pptr->port_priv_pkt_len + (size_t)pptr->port_dmacookie_sz); return (NULL); } /* * Function: fcp_icmd_free * * Description: Frees the internal command passed by the caller. * * Argument: *pptr Fcp port. * *icmd Internal packet to free. * * Return Value: None */ static void fcp_icmd_free(struct fcp_port *pptr, struct fcp_ipkt *icmd) { struct fcp_tgt *ptgt = icmd->ipkt_tgt; /* Let the underlying layers do their cleanup. */ (void) fc_ulp_uninit_packet(pptr->port_fp_handle, icmd->ipkt_fpkt); if (icmd->ipkt_fpkt->pkt_ulp_rscn_infop) { kmem_free(icmd->ipkt_fpkt->pkt_ulp_rscn_infop, sizeof (fc_ulp_rscn_info_t)); } fcp_free_dma(pptr, icmd); kmem_free(icmd, sizeof (struct fcp_ipkt) + pptr->port_priv_pkt_len + (size_t)pptr->port_dmacookie_sz); mutex_enter(&pptr->port_mutex); if (ptgt) { mutex_enter(&ptgt->tgt_mutex); ptgt->tgt_ipkt_cnt--; mutex_exit(&ptgt->tgt_mutex); } pptr->port_ipkt_cnt--; mutex_exit(&pptr->port_mutex); } /* * Function: fcp_alloc_dma * * Description: Allocated the DMA resources required for the internal * packet. * * Argument: *pptr FCP port. * *icmd Internal FCP packet. * nodma Indicates if the Cmd and Resp will be DMAed. * flags Allocation flags (Sleep or NoSleep). * * Return Value: FC_SUCCESS * FC_NOMEM */ static int fcp_alloc_dma(struct fcp_port *pptr, struct fcp_ipkt *icmd, int nodma, int flags) { int rval; size_t real_size; uint_t ccount; int bound = 0; int cmd_resp = 0; fc_packet_t *fpkt; ddi_dma_cookie_t pkt_data_cookie; ddi_dma_cookie_t *cp; uint32_t cnt; fpkt = &icmd->ipkt_fc_packet; ASSERT(fpkt->pkt_cmd_dma == NULL && fpkt->pkt_data_dma == NULL && fpkt->pkt_resp_dma == NULL); icmd->ipkt_nodma = nodma; if (nodma) { fpkt->pkt_cmd = kmem_zalloc(fpkt->pkt_cmdlen, flags); if (fpkt->pkt_cmd == NULL) { goto fail; } fpkt->pkt_resp = kmem_zalloc(fpkt->pkt_rsplen, flags); if (fpkt->pkt_resp == NULL) { goto fail; } } else { ASSERT(fpkt->pkt_cmdlen && fpkt->pkt_rsplen); rval = fcp_alloc_cmd_resp(pptr, fpkt, flags); if (rval == FC_FAILURE) { ASSERT(fpkt->pkt_cmd_dma == NULL && fpkt->pkt_resp_dma == NULL); goto fail; } cmd_resp++; } if (fpkt->pkt_datalen != 0) { /* * set up DMA handle and memory for the data in this packet */ if (ddi_dma_alloc_handle(pptr->port_dip, &pptr->port_data_dma_attr, DDI_DMA_DONTWAIT, NULL, &fpkt->pkt_data_dma) != DDI_SUCCESS) { goto fail; } if (ddi_dma_mem_alloc(fpkt->pkt_data_dma, fpkt->pkt_datalen, &pptr->port_dma_acc_attr, DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL, &fpkt->pkt_data, &real_size, &fpkt->pkt_data_acc) != DDI_SUCCESS) { goto fail; } /* was DMA mem size gotten < size asked for/needed ?? */ if (real_size < fpkt->pkt_datalen) { goto fail; } /* bind DMA address and handle together */ if (ddi_dma_addr_bind_handle(fpkt->pkt_data_dma, NULL, fpkt->pkt_data, real_size, DDI_DMA_READ | DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL, &pkt_data_cookie, &ccount) != DDI_DMA_MAPPED) { goto fail; } bound++; if (ccount > pptr->port_data_dma_attr.dma_attr_sgllen) { goto fail; } fpkt->pkt_data_cookie_cnt = ccount; cp = fpkt->pkt_data_cookie; *cp = pkt_data_cookie; cp++; for (cnt = 1; cnt < ccount; cnt++, cp++) { ddi_dma_nextcookie(fpkt->pkt_data_dma, &pkt_data_cookie); *cp = pkt_data_cookie; } } return (FC_SUCCESS); fail: if (bound) { (void) ddi_dma_unbind_handle(fpkt->pkt_data_dma); } if (fpkt->pkt_data_dma) { if (fpkt->pkt_data) { ddi_dma_mem_free(&fpkt->pkt_data_acc); } ddi_dma_free_handle(&fpkt->pkt_data_dma); } if (nodma) { if (fpkt->pkt_cmd) { kmem_free(fpkt->pkt_cmd, fpkt->pkt_cmdlen); } if (fpkt->pkt_resp) { kmem_free(fpkt->pkt_resp, fpkt->pkt_rsplen); } } else { if (cmd_resp) { fcp_free_cmd_resp(pptr, fpkt); } } return (FC_NOMEM); } static void fcp_free_dma(struct fcp_port *pptr, struct fcp_ipkt *icmd) { fc_packet_t *fpkt = icmd->ipkt_fpkt; if (fpkt->pkt_data_dma) { (void) ddi_dma_unbind_handle(fpkt->pkt_data_dma); if (fpkt->pkt_data) { ddi_dma_mem_free(&fpkt->pkt_data_acc); } ddi_dma_free_handle(&fpkt->pkt_data_dma); } if (icmd->ipkt_nodma) { if (fpkt->pkt_cmd) { kmem_free(fpkt->pkt_cmd, icmd->ipkt_cmdlen); } if (fpkt->pkt_resp) { kmem_free(fpkt->pkt_resp, icmd->ipkt_resplen); } } else { ASSERT(fpkt->pkt_resp_dma != NULL && fpkt->pkt_cmd_dma != NULL); fcp_free_cmd_resp(pptr, fpkt); } } /* * Function: fcp_lookup_target * * Description: Finds a target given a WWN. * * Argument: *pptr FCP port. * *wwn World Wide Name of the device to look for. * * Return Value: NULL No target found * Not NULL Target structure * * Context: Interrupt context. * The mutex pptr->port_mutex must be owned. */ /* ARGSUSED */ static struct fcp_tgt * fcp_lookup_target(struct fcp_port *pptr, uchar_t *wwn) { int hash; struct fcp_tgt *ptgt; ASSERT(mutex_owned(&pptr->port_mutex)); hash = FCP_HASH(wwn); for (ptgt = pptr->port_tgt_hash_table[hash]; ptgt != NULL; ptgt = ptgt->tgt_next) { if (!(ptgt->tgt_state & FCP_TGT_ORPHAN) && bcmp((caddr_t)wwn, (caddr_t)&ptgt->tgt_port_wwn.raw_wwn[0], sizeof (ptgt->tgt_port_wwn)) == 0) { break; } } return (ptgt); } /* * Find target structure given a port identifier */ static struct fcp_tgt * fcp_get_target_by_did(struct fcp_port *pptr, uint32_t d_id) { fc_portid_t port_id; la_wwn_t pwwn; struct fcp_tgt *ptgt = NULL; port_id.priv_lilp_posit = 0; port_id.port_id = d_id; if (fc_ulp_get_pwwn_by_did(pptr->port_fp_handle, port_id, &pwwn) == FC_SUCCESS) { mutex_enter(&pptr->port_mutex); ptgt = fcp_lookup_target(pptr, pwwn.raw_wwn); mutex_exit(&pptr->port_mutex); } return (ptgt); } /* * the packet completion callback routine for info cmd pkts * * this means fpkt pts to a response to either a PLOGI or a PRLI * * if there is an error an attempt is made to call a routine to resend * the command that failed */ static void fcp_icmd_callback(fc_packet_t *fpkt) { struct fcp_ipkt *icmd; struct fcp_port *pptr; struct fcp_tgt *ptgt; struct la_els_prli *prli; struct la_els_prli prli_s; struct fcp_prli *fprli; struct fcp_lun *plun; int free_pkt = 1; int rval; ls_code_t resp; uchar_t prli_acc = 0; uint32_t rscn_count = FC_INVALID_RSCN_COUNT; int lun0_newalloc; icmd = (struct fcp_ipkt *)fpkt->pkt_ulp_private; /* get ptrs to the port and target structs for the cmd */ pptr = icmd->ipkt_port; ptgt = icmd->ipkt_tgt; FCP_CP_IN(fpkt->pkt_resp, &resp, fpkt->pkt_resp_acc, sizeof (resp)); if (icmd->ipkt_opcode == LA_ELS_PRLI) { FCP_CP_IN(fpkt->pkt_cmd, &prli_s, fpkt->pkt_cmd_acc, sizeof (prli_s)); prli_acc = (prli_s.ls_code == LA_ELS_ACC); } FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "ELS (%x) callback state=0x%x reason=0x%x for %x", icmd->ipkt_opcode, fpkt->pkt_state, fpkt->pkt_reason, ptgt->tgt_d_id); if ((fpkt->pkt_state == FC_PKT_SUCCESS) && ((resp.ls_code == LA_ELS_ACC) || prli_acc)) { mutex_enter(&ptgt->tgt_mutex); if (ptgt->tgt_pd_handle == NULL) { /* * in a fabric environment the port device handles * get created only after successful LOGIN into the * transport, so the transport makes this port * device (pd) handle available in this packet, so * save it now */ ASSERT(fpkt->pkt_pd != NULL); ptgt->tgt_pd_handle = fpkt->pkt_pd; } mutex_exit(&ptgt->tgt_mutex); /* which ELS cmd is this response for ?? */ switch (icmd->ipkt_opcode) { case LA_ELS_PLOGI: FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "PLOGI to d_id=0x%x succeeded, wwn=%08x%08x", ptgt->tgt_d_id, *((int *)&ptgt->tgt_port_wwn.raw_wwn[0]), *((int *)&ptgt->tgt_port_wwn.raw_wwn[4])); FCP_TGT_TRACE(ptgt, icmd->ipkt_change_cnt, FCP_TGT_TRACE_15); /* Note that we are not allocating a new icmd */ if (fcp_send_els(pptr, ptgt, icmd, LA_ELS_PRLI, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause) != DDI_SUCCESS) { FCP_TGT_TRACE(ptgt, icmd->ipkt_change_cnt, FCP_TGT_TRACE_16); goto fail; } break; case LA_ELS_PRLI: FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "PRLI to d_id=0x%x succeeded", ptgt->tgt_d_id); FCP_TGT_TRACE(ptgt, icmd->ipkt_change_cnt, FCP_TGT_TRACE_17); prli = &prli_s; FCP_CP_IN(fpkt->pkt_resp, prli, fpkt->pkt_resp_acc, sizeof (prli_s)); fprli = (struct fcp_prli *)prli->service_params; mutex_enter(&ptgt->tgt_mutex); ptgt->tgt_icap = fprli->initiator_fn; ptgt->tgt_tcap = fprli->target_fn; mutex_exit(&ptgt->tgt_mutex); if ((fprli->type != 0x08) || (fprli->target_fn != 1)) { /* * this FCP device does not support target mode */ FCP_TGT_TRACE(ptgt, icmd->ipkt_change_cnt, FCP_TGT_TRACE_18); goto fail; } if (fprli->retry == 1) { fc_ulp_disable_relogin(pptr->port_fp_handle, &ptgt->tgt_port_wwn); } /* target is no longer offline */ mutex_enter(&pptr->port_mutex); mutex_enter(&ptgt->tgt_mutex); if (!FCP_TGT_STATE_CHANGED(ptgt, icmd)) { ptgt->tgt_state &= ~(FCP_TGT_OFFLINE | FCP_TGT_MARK); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_icmd_callback,1: state change " " occured for D_ID=0x%x", ptgt->tgt_d_id); mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); goto fail; } mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); /* * lun 0 should always respond to inquiry, so * get the LUN struct for LUN 0 * * Currently we deal with first level of addressing. * If / when we start supporting 0x device types * (DTYPE_ARRAY_CTRL, i.e. array controllers) * this logic will need revisiting. */ lun0_newalloc = 0; if ((plun = fcp_get_lun(ptgt, 0)) == NULL) { /* * no LUN struct for LUN 0 yet exists, * so create one */ plun = fcp_alloc_lun(ptgt); if (plun == NULL) { fcp_log(CE_WARN, pptr->port_dip, "!Failed to allocate lun 0 for" " D_ID=%x", ptgt->tgt_d_id); goto fail; } lun0_newalloc = 1; } /* fill in LUN info */ mutex_enter(&ptgt->tgt_mutex); /* * consider lun 0 as device not connected if it is * offlined or newly allocated */ if ((plun->lun_state & FCP_LUN_OFFLINE) || lun0_newalloc) { plun->lun_state |= FCP_LUN_DEVICE_NOT_CONNECTED; } plun->lun_state |= (FCP_LUN_BUSY | FCP_LUN_MARK); plun->lun_state &= ~FCP_LUN_OFFLINE; ptgt->tgt_lun_cnt = 1; ptgt->tgt_report_lun_cnt = 0; mutex_exit(&ptgt->tgt_mutex); /* Retrieve the rscn count (if a valid one exists) */ if (icmd->ipkt_fpkt->pkt_ulp_rscn_infop != NULL) { rscn_count = ((fc_ulp_rscn_info_t *) (icmd->ipkt_fpkt->pkt_ulp_rscn_infop)) ->ulp_rscn_count; } else { rscn_count = FC_INVALID_RSCN_COUNT; } /* send Report Lun request to target */ if (fcp_send_scsi(plun, SCMD_REPORT_LUN, sizeof (struct fcp_reportlun_resp), icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause, rscn_count) != DDI_SUCCESS) { mutex_enter(&pptr->port_mutex); if (!FCP_TGT_STATE_CHANGED(ptgt, icmd)) { fcp_log(CE_WARN, pptr->port_dip, "!Failed to send REPORT LUN to" " D_ID=%x", ptgt->tgt_d_id); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "fcp_icmd_callback,2:state change" " occured for D_ID=0x%x", ptgt->tgt_d_id); } mutex_exit(&pptr->port_mutex); FCP_TGT_TRACE(ptgt, icmd->ipkt_change_cnt, FCP_TGT_TRACE_19); goto fail; } else { free_pkt = 0; fcp_icmd_free(pptr, icmd); } break; default: fcp_log(CE_WARN, pptr->port_dip, "!fcp_icmd_callback Invalid opcode"); goto fail; } return; } /* * Other PLOGI failures are not retried as the * transport does it already */ if (icmd->ipkt_opcode != LA_ELS_PLOGI) { if (fcp_is_retryable(icmd) && icmd->ipkt_retries++ < FCP_MAX_RETRIES) { if (FCP_MUST_RETRY(fpkt)) { fcp_queue_ipkt(pptr, fpkt); return; } FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "ELS PRLI is retried for d_id=0x%x, state=%x," " reason= %x", ptgt->tgt_d_id, fpkt->pkt_state, fpkt->pkt_reason); /* * Retry by recalling the routine that * originally queued this packet */ mutex_enter(&pptr->port_mutex); if (!FCP_TGT_STATE_CHANGED(ptgt, icmd)) { caddr_t msg; mutex_exit(&pptr->port_mutex); ASSERT(icmd->ipkt_opcode != LA_ELS_PLOGI); if (fpkt->pkt_state == FC_PKT_TIMEOUT) { fpkt->pkt_timeout += FCP_TIMEOUT_DELTA; } rval = fc_ulp_issue_els(pptr->port_fp_handle, fpkt); if (rval == FC_SUCCESS) { return; } if (rval == FC_STATEC_BUSY || rval == FC_OFFLINE) { fcp_queue_ipkt(pptr, fpkt); return; } (void) fc_ulp_error(rval, &msg); fcp_log(CE_NOTE, pptr->port_dip, "!ELS 0x%x failed to d_id=0x%x;" " %s", icmd->ipkt_opcode, ptgt->tgt_d_id, msg); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_icmd_callback,3: state change " " occured for D_ID=0x%x", ptgt->tgt_d_id); mutex_exit(&pptr->port_mutex); } } } else { if (fcp_is_retryable(icmd) && icmd->ipkt_retries++ < FCP_MAX_RETRIES) { if (FCP_MUST_RETRY(fpkt)) { fcp_queue_ipkt(pptr, fpkt); return; } } mutex_enter(&pptr->port_mutex); if (!FCP_TGT_STATE_CHANGED(ptgt, icmd) && fpkt->pkt_state != FC_PKT_PORT_OFFLINE) { mutex_exit(&pptr->port_mutex); fcp_print_error(fpkt); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_icmd_callback,4: state change occured" " for D_ID=0x%x", ptgt->tgt_d_id); mutex_exit(&pptr->port_mutex); } } fail: if (free_pkt) { (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); fcp_icmd_free(pptr, icmd); } } /* * called internally to send an info cmd using the transport * * sends either an INQ or a REPORT_LUN * * when the packet is completed fcp_scsi_callback is called */ static int fcp_send_scsi(struct fcp_lun *plun, uchar_t opcode, int alloc_len, int lcount, int tcount, int cause, uint32_t rscn_count) { int nodma; struct fcp_ipkt *icmd; struct fcp_tgt *ptgt; struct fcp_port *pptr; fc_frame_hdr_t *hp; fc_packet_t *fpkt; struct fcp_cmd fcp_cmd; struct fcp_cmd *fcmd; union scsi_cdb *scsi_cdb; ASSERT(plun != NULL); ptgt = plun->lun_tgt; ASSERT(ptgt != NULL); pptr = ptgt->tgt_port; ASSERT(pptr != NULL); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "fcp_send_scsi: d_id=0x%x opcode=0x%x", ptgt->tgt_d_id, opcode); nodma = (pptr->port_fcp_dma == FC_NO_DVMA_SPACE) ? 1 : 0; icmd = fcp_icmd_alloc(pptr, ptgt, sizeof (struct fcp_cmd), FCP_MAX_RSP_IU_SIZE, alloc_len, nodma, lcount, tcount, cause, rscn_count); if (icmd == NULL) { return (DDI_FAILURE); } fpkt = icmd->ipkt_fpkt; fpkt->pkt_tran_flags = FC_TRAN_CLASS3 | FC_TRAN_INTR; icmd->ipkt_retries = 0; icmd->ipkt_opcode = opcode; icmd->ipkt_lun = plun; if (nodma) { fcmd = (struct fcp_cmd *)fpkt->pkt_cmd; } else { fcmd = &fcp_cmd; } bzero(fcmd, sizeof (struct fcp_cmd)); fpkt->pkt_timeout = FCP_SCSI_CMD_TIMEOUT; hp = &fpkt->pkt_cmd_fhdr; hp->s_id = pptr->port_id; hp->d_id = ptgt->tgt_d_id; hp->r_ctl = R_CTL_COMMAND; hp->type = FC_TYPE_SCSI_FCP; hp->f_ctl = F_CTL_SEQ_INITIATIVE | F_CTL_FIRST_SEQ; hp->rsvd = 0; hp->seq_id = 0; hp->seq_cnt = 0; hp->ox_id = 0xffff; hp->rx_id = 0xffff; hp->ro = 0; bcopy(&(plun->lun_addr), &(fcmd->fcp_ent_addr), FCP_LUN_SIZE); /* * Request SCSI target for expedited processing */ /* * Set up for untagged queuing because we do not * know if the fibre device supports queuing. */ fcmd->fcp_cntl.cntl_reserved_0 = 0; fcmd->fcp_cntl.cntl_reserved_1 = 0; fcmd->fcp_cntl.cntl_reserved_2 = 0; fcmd->fcp_cntl.cntl_reserved_3 = 0; fcmd->fcp_cntl.cntl_reserved_4 = 0; fcmd->fcp_cntl.cntl_qtype = FCP_QTYPE_UNTAGGED; scsi_cdb = (union scsi_cdb *)fcmd->fcp_cdb; switch (opcode) { case SCMD_INQUIRY_PAGE83: /* * Prepare to get the Inquiry VPD page 83 information */ fcmd->fcp_cntl.cntl_read_data = 1; fcmd->fcp_cntl.cntl_write_data = 0; fcmd->fcp_data_len = alloc_len; fpkt->pkt_tran_type = FC_PKT_FCP_READ; fpkt->pkt_comp = fcp_scsi_callback; scsi_cdb->scc_cmd = SCMD_INQUIRY; scsi_cdb->g0_addr2 = 0x01; scsi_cdb->g0_addr1 = 0x83; scsi_cdb->g0_count0 = (uchar_t)alloc_len; break; case SCMD_INQUIRY: fcmd->fcp_cntl.cntl_read_data = 1; fcmd->fcp_cntl.cntl_write_data = 0; fcmd->fcp_data_len = alloc_len; fpkt->pkt_tran_type = FC_PKT_FCP_READ; fpkt->pkt_comp = fcp_scsi_callback; scsi_cdb->scc_cmd = SCMD_INQUIRY; scsi_cdb->g0_count0 = SUN_INQSIZE; break; case SCMD_REPORT_LUN: { fc_portid_t d_id; opaque_t fca_dev; ASSERT(alloc_len >= 16); d_id.priv_lilp_posit = 0; d_id.port_id = ptgt->tgt_d_id; fca_dev = fc_ulp_get_fca_device(pptr->port_fp_handle, d_id); mutex_enter(&ptgt->tgt_mutex); ptgt->tgt_fca_dev = fca_dev; mutex_exit(&ptgt->tgt_mutex); fcmd->fcp_cntl.cntl_read_data = 1; fcmd->fcp_cntl.cntl_write_data = 0; fcmd->fcp_data_len = alloc_len; fpkt->pkt_tran_type = FC_PKT_FCP_READ; fpkt->pkt_comp = fcp_scsi_callback; scsi_cdb->scc_cmd = SCMD_REPORT_LUN; scsi_cdb->scc5_count0 = alloc_len & 0xff; scsi_cdb->scc5_count1 = (alloc_len >> 8) & 0xff; scsi_cdb->scc5_count2 = (alloc_len >> 16) & 0xff; scsi_cdb->scc5_count3 = (alloc_len >> 24) & 0xff; break; } default: fcp_log(CE_WARN, pptr->port_dip, "!fcp_send_scsi Invalid opcode"); break; } if (!nodma) { FCP_CP_OUT((uint8_t *)fcmd, fpkt->pkt_cmd, fpkt->pkt_cmd_acc, sizeof (struct fcp_cmd)); } mutex_enter(&pptr->port_mutex); if (!FCP_TGT_STATE_CHANGED(ptgt, icmd)) { mutex_exit(&pptr->port_mutex); if (fcp_transport(pptr->port_fp_handle, fpkt, 1) != FC_SUCCESS) { fcp_icmd_free(pptr, icmd); return (DDI_FAILURE); } return (DDI_SUCCESS); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_send_scsi,1: state change occured" " for D_ID=0x%x", ptgt->tgt_d_id); mutex_exit(&pptr->port_mutex); fcp_icmd_free(pptr, icmd); return (DDI_FAILURE); } } /* * called by fcp_scsi_callback to check to handle the case where * REPORT_LUN returns ILLEGAL REQUEST or a UNIT ATTENTION */ static int fcp_check_reportlun(struct fcp_rsp *rsp, fc_packet_t *fpkt) { uchar_t rqlen; int rval = DDI_FAILURE; struct scsi_extended_sense sense_info, *sense; struct fcp_ipkt *icmd = (struct fcp_ipkt *) fpkt->pkt_ulp_private; struct fcp_tgt *ptgt = icmd->ipkt_tgt; struct fcp_port *pptr = ptgt->tgt_port; ASSERT(icmd->ipkt_opcode == SCMD_REPORT_LUN); if (rsp->fcp_u.fcp_status.scsi_status == STATUS_RESERVATION_CONFLICT) { /* * SCSI-II Reserve Release support. Some older FC drives return * Reservation conflict for Report Luns command. */ if (icmd->ipkt_nodma) { rsp->fcp_u.fcp_status.rsp_len_set = 0; rsp->fcp_u.fcp_status.sense_len_set = 0; rsp->fcp_u.fcp_status.scsi_status = STATUS_GOOD; } else { fcp_rsp_t new_resp; FCP_CP_IN(fpkt->pkt_resp, &new_resp, fpkt->pkt_resp_acc, sizeof (new_resp)); new_resp.fcp_u.fcp_status.rsp_len_set = 0; new_resp.fcp_u.fcp_status.sense_len_set = 0; new_resp.fcp_u.fcp_status.scsi_status = STATUS_GOOD; FCP_CP_OUT(&new_resp, fpkt->pkt_resp, fpkt->pkt_resp_acc, sizeof (new_resp)); } FCP_CP_OUT(fcp_dummy_lun, fpkt->pkt_data, fpkt->pkt_data_acc, sizeof (fcp_dummy_lun)); return (DDI_SUCCESS); } sense = &sense_info; if (!rsp->fcp_u.fcp_status.sense_len_set) { /* no need to continue if sense length is not set */ return (rval); } /* casting 64-bit integer to 8-bit */ rqlen = (uchar_t)min(rsp->fcp_sense_len, sizeof (struct scsi_extended_sense)); if (rqlen < 14) { /* no need to continue if request length isn't long enough */ return (rval); } if (icmd->ipkt_nodma) { /* * We can safely use fcp_response_len here since the * only path that calls fcp_check_reportlun, * fcp_scsi_callback, has already called * fcp_validate_fcp_response. */ sense = (struct scsi_extended_sense *)(fpkt->pkt_resp + sizeof (struct fcp_rsp) + rsp->fcp_response_len); } else { FCP_CP_IN(fpkt->pkt_resp + sizeof (struct fcp_rsp) + rsp->fcp_response_len, sense, fpkt->pkt_resp_acc, sizeof (struct scsi_extended_sense)); } if (!FCP_SENSE_NO_LUN(sense)) { mutex_enter(&ptgt->tgt_mutex); /* clear the flag if any */ ptgt->tgt_state &= ~FCP_TGT_ILLREQ; mutex_exit(&ptgt->tgt_mutex); } if ((sense->es_key == KEY_ILLEGAL_REQUEST) && (sense->es_add_code == 0x20)) { if (icmd->ipkt_nodma) { rsp->fcp_u.fcp_status.rsp_len_set = 0; rsp->fcp_u.fcp_status.sense_len_set = 0; rsp->fcp_u.fcp_status.scsi_status = STATUS_GOOD; } else { fcp_rsp_t new_resp; FCP_CP_IN(fpkt->pkt_resp, &new_resp, fpkt->pkt_resp_acc, sizeof (new_resp)); new_resp.fcp_u.fcp_status.rsp_len_set = 0; new_resp.fcp_u.fcp_status.sense_len_set = 0; new_resp.fcp_u.fcp_status.scsi_status = STATUS_GOOD; FCP_CP_OUT(&new_resp, fpkt->pkt_resp, fpkt->pkt_resp_acc, sizeof (new_resp)); } FCP_CP_OUT(fcp_dummy_lun, fpkt->pkt_data, fpkt->pkt_data_acc, sizeof (fcp_dummy_lun)); return (DDI_SUCCESS); } /* * This is for the STK library which returns a check condition, * to indicate device is not ready, manual assistance needed. * This is to a report lun command when the door is open. */ if ((sense->es_key == KEY_NOT_READY) && (sense->es_add_code == 0x04)) { if (icmd->ipkt_nodma) { rsp->fcp_u.fcp_status.rsp_len_set = 0; rsp->fcp_u.fcp_status.sense_len_set = 0; rsp->fcp_u.fcp_status.scsi_status = STATUS_GOOD; } else { fcp_rsp_t new_resp; FCP_CP_IN(fpkt->pkt_resp, &new_resp, fpkt->pkt_resp_acc, sizeof (new_resp)); new_resp.fcp_u.fcp_status.rsp_len_set = 0; new_resp.fcp_u.fcp_status.sense_len_set = 0; new_resp.fcp_u.fcp_status.scsi_status = STATUS_GOOD; FCP_CP_OUT(&new_resp, fpkt->pkt_resp, fpkt->pkt_resp_acc, sizeof (new_resp)); } FCP_CP_OUT(fcp_dummy_lun, fpkt->pkt_data, fpkt->pkt_data_acc, sizeof (fcp_dummy_lun)); return (DDI_SUCCESS); } if ((FCP_SENSE_REPORTLUN_CHANGED(sense)) || (FCP_SENSE_NO_LUN(sense))) { mutex_enter(&ptgt->tgt_mutex); if ((FCP_SENSE_NO_LUN(sense)) && (ptgt->tgt_state & FCP_TGT_ILLREQ)) { ptgt->tgt_state &= ~FCP_TGT_ILLREQ; mutex_exit(&ptgt->tgt_mutex); /* * reconfig was triggred by ILLEGAL REQUEST but * got ILLEGAL REQUEST again */ FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "!FCP: Unable to obtain Report Lun data" " target=%x", ptgt->tgt_d_id); } else { if (ptgt->tgt_tid == NULL) { timeout_id_t tid; /* * REPORT LUN data has changed. Kick off * rediscovery */ tid = timeout(fcp_reconfigure_luns, (caddr_t)ptgt, (clock_t)drv_usectohz(1)); ptgt->tgt_tid = tid; ptgt->tgt_state |= FCP_TGT_BUSY; } if (FCP_SENSE_NO_LUN(sense)) { ptgt->tgt_state |= FCP_TGT_ILLREQ; } mutex_exit(&ptgt->tgt_mutex); if (FCP_SENSE_REPORTLUN_CHANGED(sense)) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "!FCP:Report Lun Has Changed" " target=%x", ptgt->tgt_d_id); } else if (FCP_SENSE_NO_LUN(sense)) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "!FCP:LU Not Supported" " target=%x", ptgt->tgt_d_id); } } rval = DDI_SUCCESS; } FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "D_ID=%x, sense=%x, status=%x", fpkt->pkt_cmd_fhdr.d_id, sense->es_key, rsp->fcp_u.fcp_status.scsi_status); return (rval); } /* * Function: fcp_scsi_callback * * Description: This is the callback routine set by fcp_send_scsi() after * it calls fcp_icmd_alloc(). The SCSI command completed here * and autogenerated by FCP are: REPORT_LUN, INQUIRY and * INQUIRY_PAGE83. * * Argument: *fpkt FC packet used to convey the command * * Return Value: None */ static void fcp_scsi_callback(fc_packet_t *fpkt) { struct fcp_ipkt *icmd = (struct fcp_ipkt *) fpkt->pkt_ulp_private; struct fcp_rsp_info fcp_rsp_err, *bep; struct fcp_port *pptr; struct fcp_tgt *ptgt; struct fcp_lun *plun; struct fcp_rsp response, *rsp; if (icmd->ipkt_nodma) { rsp = (struct fcp_rsp *)fpkt->pkt_resp; } else { rsp = &response; FCP_CP_IN(fpkt->pkt_resp, rsp, fpkt->pkt_resp_acc, sizeof (struct fcp_rsp)); } ptgt = icmd->ipkt_tgt; pptr = ptgt->tgt_port; plun = icmd->ipkt_lun; FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "SCSI callback state=0x%x for %x, op_code=0x%x, " "status=%x, lun num=%x", fpkt->pkt_state, ptgt->tgt_d_id, icmd->ipkt_opcode, rsp->fcp_u.fcp_status.scsi_status, plun->lun_num); /* * Pre-init LUN GUID with NWWN if it is not a device that * supports multiple luns and we know it's not page83 * compliant. Although using a NWWN is not lun unique, * we will be fine since there is only one lun behind the taget * in this case. */ if ((plun->lun_guid_size == 0) && (icmd->ipkt_opcode == SCMD_INQUIRY_PAGE83) && (fcp_symmetric_device_probe(plun) == 0)) { char ascii_wwn[FC_WWN_SIZE*2+1]; fcp_wwn_to_ascii(&ptgt->tgt_node_wwn.raw_wwn[0], ascii_wwn); (void) fcp_copy_guid_2_lun_block(plun, ascii_wwn); } /* * Some old FC tapes and FC <-> SCSI bridge devices return overrun * when thay have more data than what is asked in CDB. An overrun * is really when FCP_DL is smaller than the data length in CDB. * In the case here we know that REPORT LUN command we formed within * this binary has correct FCP_DL. So this OVERRUN is due to bad device * behavior. In reality this is FC_SUCCESS. */ if ((fpkt->pkt_state != FC_PKT_SUCCESS) && (fpkt->pkt_reason == FC_REASON_OVERRUN) && (icmd->ipkt_opcode == SCMD_REPORT_LUN)) { fpkt->pkt_state = FC_PKT_SUCCESS; } if (fpkt->pkt_state != FC_PKT_SUCCESS) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "icmd failed with state=0x%x for %x", fpkt->pkt_state, ptgt->tgt_d_id); if (fpkt->pkt_reason == FC_REASON_CRC_ERROR) { /* * Inquiry VPD page command on A5K SES devices would * result in data CRC errors. */ if (icmd->ipkt_opcode == SCMD_INQUIRY_PAGE83) { (void) fcp_handle_page83(fpkt, icmd, 1); return; } } if (fpkt->pkt_state == FC_PKT_TIMEOUT || FCP_MUST_RETRY(fpkt)) { fpkt->pkt_timeout += FCP_TIMEOUT_DELTA; fcp_retry_scsi_cmd(fpkt); return; } FCP_TGT_TRACE(ptgt, icmd->ipkt_change_cnt, FCP_TGT_TRACE_20); mutex_enter(&pptr->port_mutex); mutex_enter(&ptgt->tgt_mutex); if (!FCP_STATE_CHANGED(pptr, ptgt, icmd)) { mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); fcp_print_error(fpkt); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_scsi_callback,1: state change occured" " for D_ID=0x%x", ptgt->tgt_d_id); mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); } (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); fcp_icmd_free(pptr, icmd); return; } FCP_TGT_TRACE(ptgt, icmd->ipkt_change_cnt, FCP_TGT_TRACE_21); mutex_enter(&pptr->port_mutex); mutex_enter(&ptgt->tgt_mutex); if (FCP_STATE_CHANGED(pptr, ptgt, icmd)) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_scsi_callback,2: state change occured" " for D_ID=0x%x", ptgt->tgt_d_id); mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); fcp_icmd_free(pptr, icmd); return; } ASSERT((ptgt->tgt_state & FCP_TGT_MARK) == 0); mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); if (icmd->ipkt_nodma) { bep = (struct fcp_rsp_info *)(fpkt->pkt_resp + sizeof (struct fcp_rsp)); } else { bep = &fcp_rsp_err; FCP_CP_IN(fpkt->pkt_resp + sizeof (struct fcp_rsp), bep, fpkt->pkt_resp_acc, sizeof (struct fcp_rsp_info)); } if (fcp_validate_fcp_response(rsp, pptr) != FC_SUCCESS) { fcp_retry_scsi_cmd(fpkt); return; } if (rsp->fcp_u.fcp_status.rsp_len_set && bep->rsp_code != FCP_NO_FAILURE) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "rsp_code=0x%x, rsp_len_set=0x%x", bep->rsp_code, rsp->fcp_u.fcp_status.rsp_len_set); fcp_retry_scsi_cmd(fpkt); return; } if (rsp->fcp_u.fcp_status.scsi_status == STATUS_QFULL || rsp->fcp_u.fcp_status.scsi_status == STATUS_BUSY) { fcp_queue_ipkt(pptr, fpkt); return; } /* * Devices that do not support INQUIRY_PAGE83, return check condition * with illegal request as per SCSI spec. * Crossbridge is one such device and Daktari's SES node is another. * We want to ideally enumerate these devices as a non-mpxio devices. * SES nodes (Daktari only currently) are an exception to this. */ if ((icmd->ipkt_opcode == SCMD_INQUIRY_PAGE83) && (rsp->fcp_u.fcp_status.scsi_status & STATUS_CHECK)) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "INQUIRY_PAGE83 for d_id %x (dtype:0x%x) failed with " "check condition. May enumerate as non-mpxio device", ptgt->tgt_d_id, plun->lun_type); /* * If we let Daktari's SES be enumerated as a non-mpxio * device, there will be a discrepency in that the other * internal FC disks will get enumerated as mpxio devices. * Applications like luxadm expect this to be consistent. * * So, we put in a hack here to check if this is an SES device * and handle it here. */ if (plun->lun_type == DTYPE_ESI) { /* * Since, pkt_state is actually FC_PKT_SUCCESS * at this stage, we fake a failure here so that * fcp_handle_page83 will create a device path using * the WWN instead of the GUID which is not there anyway */ fpkt->pkt_state = FC_PKT_LOCAL_RJT; (void) fcp_handle_page83(fpkt, icmd, 1); return; } mutex_enter(&ptgt->tgt_mutex); plun->lun_state &= ~(FCP_LUN_OFFLINE | FCP_LUN_MARK | FCP_LUN_BUSY); mutex_exit(&ptgt->tgt_mutex); (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); fcp_icmd_free(pptr, icmd); return; } if (rsp->fcp_u.fcp_status.scsi_status != STATUS_GOOD) { int rval = DDI_FAILURE; /* * handle cases where report lun isn't supported * by faking up our own REPORT_LUN response or * UNIT ATTENTION */ if (icmd->ipkt_opcode == SCMD_REPORT_LUN) { rval = fcp_check_reportlun(rsp, fpkt); /* * fcp_check_reportlun might have modified the * FCP response. Copy it in again to get an updated * FCP response */ if (rval == DDI_SUCCESS && icmd->ipkt_nodma == 0) { rsp = &response; FCP_CP_IN(fpkt->pkt_resp, rsp, fpkt->pkt_resp_acc, sizeof (struct fcp_rsp)); } } if (rsp->fcp_u.fcp_status.scsi_status != STATUS_GOOD) { if (rval == DDI_SUCCESS) { (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); fcp_icmd_free(pptr, icmd); } else { fcp_retry_scsi_cmd(fpkt); } return; } } else { if (icmd->ipkt_opcode == SCMD_REPORT_LUN) { mutex_enter(&ptgt->tgt_mutex); ptgt->tgt_state &= ~FCP_TGT_ILLREQ; mutex_exit(&ptgt->tgt_mutex); } } ASSERT(rsp->fcp_u.fcp_status.scsi_status == STATUS_GOOD); (void) ddi_dma_sync(fpkt->pkt_data_dma, 0, 0, DDI_DMA_SYNC_FORCPU); switch (icmd->ipkt_opcode) { case SCMD_INQUIRY: FCP_LUN_TRACE(plun, FCP_LUN_TRACE_1); fcp_handle_inquiry(fpkt, icmd); break; case SCMD_REPORT_LUN: FCP_TGT_TRACE(ptgt, icmd->ipkt_change_cnt, FCP_TGT_TRACE_22); fcp_handle_reportlun(fpkt, icmd); break; case SCMD_INQUIRY_PAGE83: FCP_LUN_TRACE(plun, FCP_LUN_TRACE_2); (void) fcp_handle_page83(fpkt, icmd, 0); break; default: fcp_log(CE_WARN, NULL, "!Invalid SCSI opcode"); (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); fcp_icmd_free(pptr, icmd); break; } } static void fcp_retry_scsi_cmd(fc_packet_t *fpkt) { struct fcp_ipkt *icmd = (struct fcp_ipkt *) fpkt->pkt_ulp_private; struct fcp_tgt *ptgt = icmd->ipkt_tgt; struct fcp_port *pptr = ptgt->tgt_port; if (icmd->ipkt_retries < FCP_MAX_RETRIES && fcp_is_retryable(icmd)) { mutex_enter(&pptr->port_mutex); if (!FCP_TGT_STATE_CHANGED(ptgt, icmd)) { mutex_exit(&pptr->port_mutex); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "Retrying %s to %x; state=%x, reason=%x", (icmd->ipkt_opcode == SCMD_REPORT_LUN) ? "Report LUN" : "INQUIRY", ptgt->tgt_d_id, fpkt->pkt_state, fpkt->pkt_reason); fcp_queue_ipkt(pptr, fpkt); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_retry_scsi_cmd,1: state change occured" " for D_ID=0x%x", ptgt->tgt_d_id); mutex_exit(&pptr->port_mutex); (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); fcp_icmd_free(pptr, icmd); } } else { fcp_print_error(fpkt); (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); fcp_icmd_free(pptr, icmd); } } /* * Function: fcp_handle_page83 * * Description: Treats the response to INQUIRY_PAGE83. * * Argument: *fpkt FC packet used to convey the command. * *icmd Original fcp_ipkt structure. * ignore_page83_data * if it's 1, that means it's a special devices's * page83 response, it should be enumerated under mpxio * * Return Value: None */ static void fcp_handle_page83(fc_packet_t *fpkt, struct fcp_ipkt *icmd, int ignore_page83_data) { struct fcp_port *pptr; struct fcp_lun *plun; struct fcp_tgt *ptgt; uchar_t dev_id_page[SCMD_MAX_INQUIRY_PAGE83_SIZE]; int fail = 0; ddi_devid_t devid; char *guid = NULL; int ret; ASSERT(icmd != NULL && fpkt != NULL); pptr = icmd->ipkt_port; ptgt = icmd->ipkt_tgt; plun = icmd->ipkt_lun; if (fpkt->pkt_state == FC_PKT_SUCCESS) { FCP_LUN_TRACE(plun, FCP_LUN_TRACE_7); FCP_CP_IN(fpkt->pkt_data, dev_id_page, fpkt->pkt_data_acc, SCMD_MAX_INQUIRY_PAGE83_SIZE); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "fcp_handle_page83: port=%d, tgt D_ID=0x%x, " "dtype=0x%x, lun num=%x", pptr->port_instance, ptgt->tgt_d_id, dev_id_page[0], plun->lun_num); ret = ddi_devid_scsi_encode( DEVID_SCSI_ENCODE_VERSION_LATEST, NULL, /* driver name */ (unsigned char *) &plun->lun_inq, /* standard inquiry */ sizeof (plun->lun_inq), /* size of standard inquiry */ NULL, /* page 80 data */ 0, /* page 80 len */ dev_id_page, /* page 83 data */ SCMD_MAX_INQUIRY_PAGE83_SIZE, /* page 83 data len */ &devid); if (ret == DDI_SUCCESS) { guid = ddi_devid_to_guid(devid); if (guid) { /* * Check our current guid. If it's non null * and it has changed, we need to copy it into * lun_old_guid since we might still need it. */ if (plun->lun_guid && strcmp(guid, plun->lun_guid)) { unsigned int len; /* * If the guid of the LUN changes, * reconfiguration should be triggered * to reflect the changes. * i.e. we should offline the LUN with * the old guid, and online the LUN with * the new guid. */ plun->lun_state |= FCP_LUN_CHANGED; if (plun->lun_old_guid) { kmem_free(plun->lun_old_guid, plun->lun_old_guid_size); } len = plun->lun_guid_size; plun->lun_old_guid_size = len; plun->lun_old_guid = kmem_zalloc(len, KM_NOSLEEP); if (plun->lun_old_guid) { /* * The alloc was successful then * let's do the copy. */ bcopy(plun->lun_guid, plun->lun_old_guid, len); } else { fail = 1; plun->lun_old_guid_size = 0; } } if (!fail) { if (fcp_copy_guid_2_lun_block( plun, guid)) { fail = 1; } } ddi_devid_free_guid(guid); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_handle_page83: unable to create " "GUID"); /* couldn't create good guid from devid */ fail = 1; } ddi_devid_free(devid); } else if (ret == DDI_NOT_WELL_FORMED) { /* NULL filled data for page 83 */ FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_handle_page83: retry GUID"); icmd->ipkt_retries = 0; fcp_retry_scsi_cmd(fpkt); return; } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_handle_page83: bad ddi_devid_scsi_encode %x", ret); /* * Since the page83 validation * introduced late, we are being * tolerant to the existing devices * that already found to be working * under mpxio, like A5200's SES device, * its page83 response will not be standard-compliant, * but we still want it to be enumerated under mpxio. */ if (fcp_symmetric_device_probe(plun) != 0) { fail = 1; } } } else { /* bad packet state */ FCP_LUN_TRACE(plun, FCP_LUN_TRACE_8); /* * For some special devices (A5K SES and Daktari's SES devices), * they should be enumerated under mpxio * or "luxadm dis" will fail */ if (ignore_page83_data) { fail = 0; } else { fail = 1; } FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "!Devid page cmd failed. " "fpkt_state: %x fpkt_reason: %x", "ignore_page83: %d", fpkt->pkt_state, fpkt->pkt_reason, ignore_page83_data); } mutex_enter(&pptr->port_mutex); mutex_enter(&plun->lun_mutex); /* * If lun_cip is not NULL, then we needn't update lun_mpxio to avoid * mismatch between lun_cip and lun_mpxio. */ if (plun->lun_cip == NULL) { /* * If we don't have a guid for this lun it's because we were * unable to glean one from the page 83 response. Set the * control flag to 0 here to make sure that we don't attempt to * enumerate it under mpxio. */ if (fail || pptr->port_mpxio == 0) { plun->lun_mpxio = 0; } else { plun->lun_mpxio = 1; } } mutex_exit(&plun->lun_mutex); mutex_exit(&pptr->port_mutex); mutex_enter(&ptgt->tgt_mutex); plun->lun_state &= ~(FCP_LUN_OFFLINE | FCP_LUN_MARK | FCP_LUN_BUSY); mutex_exit(&ptgt->tgt_mutex); (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); fcp_icmd_free(pptr, icmd); } /* * Function: fcp_handle_inquiry * * Description: Called by fcp_scsi_callback to handle the response to an * INQUIRY request. * * Argument: *fpkt FC packet used to convey the command. * *icmd Original fcp_ipkt structure. * * Return Value: None */ static void fcp_handle_inquiry(fc_packet_t *fpkt, struct fcp_ipkt *icmd) { struct fcp_port *pptr; struct fcp_lun *plun; struct fcp_tgt *ptgt; uchar_t dtype; uchar_t pqual; uint32_t rscn_count = FC_INVALID_RSCN_COUNT; ASSERT(icmd != NULL && fpkt != NULL); pptr = icmd->ipkt_port; ptgt = icmd->ipkt_tgt; plun = icmd->ipkt_lun; FCP_CP_IN(fpkt->pkt_data, &plun->lun_inq, fpkt->pkt_data_acc, sizeof (struct scsi_inquiry)); dtype = plun->lun_inq.inq_dtype & DTYPE_MASK; pqual = plun->lun_inq.inq_dtype >> 5; FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "fcp_handle_inquiry: port=%d, tgt D_ID=0x%x, lun=0x%x, " "dtype=0x%x pqual: 0x%x", pptr->port_instance, ptgt->tgt_d_id, plun->lun_num, dtype, pqual); if (pqual != 0) { /* * Non-zero peripheral qualifier */ fcp_log(CE_CONT, pptr->port_dip, "!Target 0x%x lun 0x%x: Nonzero peripheral qualifier: " "Device type=0x%x Peripheral qual=0x%x\n", ptgt->tgt_d_id, plun->lun_num, dtype, pqual); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "!Target 0x%x lun 0x%x: Nonzero peripheral qualifier: " "Device type=0x%x Peripheral qual=0x%x\n", ptgt->tgt_d_id, plun->lun_num, dtype, pqual); FCP_LUN_TRACE(plun, FCP_LUN_TRACE_3); (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); fcp_icmd_free(pptr, icmd); return; } /* * If the device is already initialized, check the dtype * for a change. If it has changed then update the flags * so the create_luns will offline the old device and * create the new device. Refer to bug: 4764752 */ if ((plun->lun_state & FCP_LUN_INIT) && dtype != plun->lun_type) { plun->lun_state |= FCP_LUN_CHANGED; } plun->lun_type = plun->lun_inq.inq_dtype; /* * This code is setting/initializing the throttling in the FCA * driver. */ mutex_enter(&pptr->port_mutex); if (!pptr->port_notify) { if (bcmp(plun->lun_inq.inq_pid, pid, strlen(pid)) == 0) { uint32_t cmd = 0; cmd = ((cmd & 0xFF | FC_NOTIFY_THROTTLE) | ((cmd & 0xFFFFFF00 >> 8) | FCP_SVE_THROTTLE << 8)); pptr->port_notify = 1; mutex_exit(&pptr->port_mutex); (void) fc_ulp_port_notify(pptr->port_fp_handle, cmd); mutex_enter(&pptr->port_mutex); } } if (FCP_TGT_STATE_CHANGED(ptgt, icmd)) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_handle_inquiry,1:state change occured" " for D_ID=0x%x", ptgt->tgt_d_id); mutex_exit(&pptr->port_mutex); FCP_LUN_TRACE(plun, FCP_LUN_TRACE_5); (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); fcp_icmd_free(pptr, icmd); return; } ASSERT((ptgt->tgt_state & FCP_TGT_MARK) == 0); mutex_exit(&pptr->port_mutex); /* Retrieve the rscn count (if a valid one exists) */ if (icmd->ipkt_fpkt->pkt_ulp_rscn_infop != NULL) { rscn_count = ((fc_ulp_rscn_info_t *) (icmd->ipkt_fpkt->pkt_ulp_rscn_infop))->ulp_rscn_count; } else { rscn_count = FC_INVALID_RSCN_COUNT; } if (fcp_send_scsi(plun, SCMD_INQUIRY_PAGE83, SCMD_MAX_INQUIRY_PAGE83_SIZE, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause, rscn_count) != DDI_SUCCESS) { fcp_log(CE_WARN, NULL, "!failed to send page 83"); FCP_LUN_TRACE(plun, FCP_LUN_TRACE_6); (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); } /* * Read Inquiry VPD Page 0x83 to uniquely * identify this logical unit. */ fcp_icmd_free(pptr, icmd); } /* * Function: fcp_handle_reportlun * * Description: Called by fcp_scsi_callback to handle the response to a * REPORT_LUN request. * * Argument: *fpkt FC packet used to convey the command. * *icmd Original fcp_ipkt structure. * * Return Value: None */ static void fcp_handle_reportlun(fc_packet_t *fpkt, struct fcp_ipkt *icmd) { int i; int nluns_claimed; int nluns_bufmax; int len; uint16_t lun_num; uint32_t rscn_count = FC_INVALID_RSCN_COUNT; struct fcp_port *pptr; struct fcp_tgt *ptgt; struct fcp_lun *plun; struct fcp_reportlun_resp *report_lun; pptr = icmd->ipkt_port; ptgt = icmd->ipkt_tgt; len = fpkt->pkt_datalen; if ((len < FCP_LUN_HEADER) || ((report_lun = kmem_zalloc(len, KM_NOSLEEP)) == NULL)) { (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); fcp_icmd_free(pptr, icmd); return; } FCP_CP_IN(fpkt->pkt_data, report_lun, fpkt->pkt_data_acc, fpkt->pkt_datalen); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "fcp_handle_reportlun: port=%d, tgt D_ID=0x%x", pptr->port_instance, ptgt->tgt_d_id); /* * Get the number of luns (which is supplied as LUNS * 8) the * device claims it has. */ nluns_claimed = BE_32(report_lun->num_lun) >> 3; /* * Get the maximum number of luns the buffer submitted can hold. */ nluns_bufmax = (fpkt->pkt_datalen - FCP_LUN_HEADER) / FCP_LUN_SIZE; /* * Due to limitations of certain hardware, we support only 16 bit LUNs */ if (nluns_claimed > FCP_MAX_LUNS_SUPPORTED) { kmem_free(report_lun, len); fcp_log(CE_NOTE, pptr->port_dip, "!Can not support" " 0x%x number of LUNs for target=%x", nluns_claimed, ptgt->tgt_d_id); (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); fcp_icmd_free(pptr, icmd); return; } /* * If there are more LUNs than we have allocated memory for, * allocate more space and send down yet another report lun if * the maximum number of attempts hasn't been reached. */ mutex_enter(&ptgt->tgt_mutex); if ((nluns_claimed > nluns_bufmax) && (ptgt->tgt_report_lun_cnt < FCP_MAX_REPORTLUNS_ATTEMPTS)) { struct fcp_lun *plun; ptgt->tgt_report_lun_cnt++; plun = ptgt->tgt_lun; ASSERT(plun != NULL); mutex_exit(&ptgt->tgt_mutex); kmem_free(report_lun, len); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "!Dynamically discovered %d LUNs for D_ID=%x", nluns_claimed, ptgt->tgt_d_id); /* Retrieve the rscn count (if a valid one exists) */ if (icmd->ipkt_fpkt->pkt_ulp_rscn_infop != NULL) { rscn_count = ((fc_ulp_rscn_info_t *) (icmd->ipkt_fpkt->pkt_ulp_rscn_infop))-> ulp_rscn_count; } else { rscn_count = FC_INVALID_RSCN_COUNT; } if (fcp_send_scsi(icmd->ipkt_lun, SCMD_REPORT_LUN, FCP_LUN_HEADER + (nluns_claimed * FCP_LUN_SIZE), icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause, rscn_count) != DDI_SUCCESS) { (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); } fcp_icmd_free(pptr, icmd); return; } if (nluns_claimed > nluns_bufmax) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "Target=%x:%x:%x:%x:%x:%x:%x:%x" " Number of LUNs lost=%x", ptgt->tgt_port_wwn.raw_wwn[0], ptgt->tgt_port_wwn.raw_wwn[1], ptgt->tgt_port_wwn.raw_wwn[2], ptgt->tgt_port_wwn.raw_wwn[3], ptgt->tgt_port_wwn.raw_wwn[4], ptgt->tgt_port_wwn.raw_wwn[5], ptgt->tgt_port_wwn.raw_wwn[6], ptgt->tgt_port_wwn.raw_wwn[7], nluns_claimed - nluns_bufmax); nluns_claimed = nluns_bufmax; } ptgt->tgt_lun_cnt = nluns_claimed; /* * Identify missing LUNs and print warning messages */ for (plun = ptgt->tgt_lun; plun; plun = plun->lun_next) { int offline; int exists = 0; offline = (plun->lun_state & FCP_LUN_OFFLINE) ? 1 : 0; for (i = 0; i < nluns_claimed && exists == 0; i++) { uchar_t *lun_string; lun_string = (uchar_t *)&(report_lun->lun_string[i]); switch (lun_string[0] & 0xC0) { case FCP_LUN_ADDRESSING: case FCP_PD_ADDRESSING: lun_num = ((lun_string[0] & 0x3F) << 8) | lun_string[1]; if (plun->lun_num == lun_num) { exists++; break; } break; default: break; } } if (!exists && !offline) { mutex_exit(&ptgt->tgt_mutex); mutex_enter(&pptr->port_mutex); mutex_enter(&ptgt->tgt_mutex); if (!FCP_STATE_CHANGED(pptr, ptgt, icmd)) { /* * set disappear flag when device was connected */ if (!(plun->lun_state & FCP_LUN_DEVICE_NOT_CONNECTED)) { plun->lun_state |= FCP_LUN_DISAPPEARED; } mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); if (!(plun->lun_state & FCP_LUN_DEVICE_NOT_CONNECTED)) { fcp_log(CE_NOTE, pptr->port_dip, "!Lun=%x for target=%x disappeared", plun->lun_num, ptgt->tgt_d_id); } mutex_enter(&ptgt->tgt_mutex); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "fcp_handle_reportlun,1: state change" " occured for D_ID=0x%x", ptgt->tgt_d_id); mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); kmem_free(report_lun, len); (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); fcp_icmd_free(pptr, icmd); return; } } else if (exists) { /* * clear FCP_LUN_DEVICE_NOT_CONNECTED when lun 0 * actually exists in REPORT_LUN response */ if (plun->lun_state & FCP_LUN_DEVICE_NOT_CONNECTED) { plun->lun_state &= ~FCP_LUN_DEVICE_NOT_CONNECTED; } if (offline || plun->lun_num == 0) { if (plun->lun_state & FCP_LUN_DISAPPEARED) { plun->lun_state &= ~FCP_LUN_DISAPPEARED; mutex_exit(&ptgt->tgt_mutex); fcp_log(CE_NOTE, pptr->port_dip, "!Lun=%x for target=%x reappeared", plun->lun_num, ptgt->tgt_d_id); mutex_enter(&ptgt->tgt_mutex); } } } } ptgt->tgt_tmp_cnt = nluns_claimed ? nluns_claimed : 1; mutex_exit(&ptgt->tgt_mutex); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "fcp_handle_reportlun: port=%d, tgt D_ID=0x%x, %d LUN(s)", pptr->port_instance, ptgt->tgt_d_id, nluns_claimed); /* scan each lun */ for (i = 0; i < nluns_claimed; i++) { uchar_t *lun_string; lun_string = (uchar_t *)&(report_lun->lun_string[i]); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "handle_reportlun: d_id=%x, LUN ind=%d, LUN=%d," " addr=0x%x", ptgt->tgt_d_id, i, lun_string[1], lun_string[0]); switch (lun_string[0] & 0xC0) { case FCP_LUN_ADDRESSING: case FCP_PD_ADDRESSING: lun_num = ((lun_string[0] & 0x3F) << 8) | lun_string[1]; /* We will skip masked LUNs because of the blacklist. */ if (fcp_lun_blacklist != NULL) { mutex_enter(&ptgt->tgt_mutex); if (fcp_should_mask(&ptgt->tgt_port_wwn, lun_num) == TRUE) { ptgt->tgt_lun_cnt--; mutex_exit(&ptgt->tgt_mutex); break; } mutex_exit(&ptgt->tgt_mutex); } /* see if this LUN is already allocated */ if ((plun = fcp_get_lun(ptgt, lun_num)) == NULL) { plun = fcp_alloc_lun(ptgt); if (plun == NULL) { fcp_log(CE_NOTE, pptr->port_dip, "!Lun allocation failed" " target=%x lun=%x", ptgt->tgt_d_id, lun_num); break; } } mutex_enter(&plun->lun_tgt->tgt_mutex); /* convert to LUN */ plun->lun_addr.ent_addr_0 = BE_16(*(uint16_t *)&(lun_string[0])); plun->lun_addr.ent_addr_1 = BE_16(*(uint16_t *)&(lun_string[2])); plun->lun_addr.ent_addr_2 = BE_16(*(uint16_t *)&(lun_string[4])); plun->lun_addr.ent_addr_3 = BE_16(*(uint16_t *)&(lun_string[6])); plun->lun_num = lun_num; plun->lun_state |= FCP_LUN_BUSY | FCP_LUN_MARK; plun->lun_state &= ~FCP_LUN_OFFLINE; mutex_exit(&plun->lun_tgt->tgt_mutex); /* Retrieve the rscn count (if a valid one exists) */ if (icmd->ipkt_fpkt->pkt_ulp_rscn_infop != NULL) { rscn_count = ((fc_ulp_rscn_info_t *) (icmd->ipkt_fpkt->pkt_ulp_rscn_infop))-> ulp_rscn_count; } else { rscn_count = FC_INVALID_RSCN_COUNT; } if (fcp_send_scsi(plun, SCMD_INQUIRY, SUN_INQSIZE, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause, rscn_count) != DDI_SUCCESS) { mutex_enter(&pptr->port_mutex); mutex_enter(&plun->lun_tgt->tgt_mutex); if (!FCP_STATE_CHANGED(pptr, ptgt, icmd)) { fcp_log(CE_NOTE, pptr->port_dip, "!failed to send INQUIRY" " target=%x lun=%x", ptgt->tgt_d_id, plun->lun_num); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "fcp_handle_reportlun,2: state" " change occured for D_ID=0x%x", ptgt->tgt_d_id); } mutex_exit(&plun->lun_tgt->tgt_mutex); mutex_exit(&pptr->port_mutex); } else { continue; } break; case FCP_VOLUME_ADDRESSING: /* FALLTHROUGH */ default: fcp_log(CE_WARN, NULL, "!Unsupported LUN Addressing method %x " "in response to REPORT_LUN", lun_string[0]); break; } /* * each time through this loop we should decrement * the tmp_cnt by one -- since we go through this loop * one time for each LUN, the tmp_cnt should never be <=0 */ (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); } if (i == 0) { fcp_log(CE_WARN, pptr->port_dip, "!FCP: target=%x reported NO Luns", ptgt->tgt_d_id); (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); } kmem_free(report_lun, len); fcp_icmd_free(pptr, icmd); } /* * called internally to return a LUN given a target and a LUN number */ static struct fcp_lun * fcp_get_lun(struct fcp_tgt *ptgt, uint16_t lun_num) { struct fcp_lun *plun; mutex_enter(&ptgt->tgt_mutex); for (plun = ptgt->tgt_lun; plun != NULL; plun = plun->lun_next) { if (plun->lun_num == lun_num) { mutex_exit(&ptgt->tgt_mutex); return (plun); } } mutex_exit(&ptgt->tgt_mutex); return (NULL); } /* * handle finishing one target for fcp_finish_init * * return true (non-zero) if we want finish_init to continue with the * next target * * called with the port mutex held */ /*ARGSUSED*/ static int fcp_finish_tgt(struct fcp_port *pptr, struct fcp_tgt *ptgt, int link_cnt, int tgt_cnt, int cause) { int rval = 1; ASSERT(pptr != NULL); ASSERT(ptgt != NULL); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "finish_tgt: D_ID/state = 0x%x/0x%x", ptgt->tgt_d_id, ptgt->tgt_state); ASSERT(mutex_owned(&pptr->port_mutex)); if ((pptr->port_link_cnt != link_cnt) || (tgt_cnt && ptgt->tgt_change_cnt != tgt_cnt)) { /* * oh oh -- another link reset or target change * must have occurred while we are in here */ FCP_TGT_TRACE(ptgt, tgt_cnt, FCP_TGT_TRACE_23); return (0); } else { FCP_TGT_TRACE(ptgt, tgt_cnt, FCP_TGT_TRACE_24); } mutex_enter(&ptgt->tgt_mutex); if (!(ptgt->tgt_state & FCP_TGT_OFFLINE)) { /* * tgt is not offline -- is it marked (i.e. needs * to be offlined) ?? */ if (ptgt->tgt_state & FCP_TGT_MARK) { /* * this target not offline *and* * marked */ ptgt->tgt_state &= ~FCP_TGT_MARK; rval = fcp_offline_target(pptr, ptgt, link_cnt, tgt_cnt, 0, 0); } else { ptgt->tgt_state &= ~FCP_TGT_BUSY; /* create the LUNs */ if (ptgt->tgt_node_state != FCP_TGT_NODE_ON_DEMAND) { ptgt->tgt_node_state = FCP_TGT_NODE_PRESENT; fcp_create_luns(ptgt, link_cnt, tgt_cnt, cause); ptgt->tgt_device_created = 1; } else { fcp_update_tgt_state(ptgt, FCP_RESET, FCP_LUN_BUSY); } } } mutex_exit(&ptgt->tgt_mutex); return (rval); } /* * this routine is called to finish port initialization * * Each port has a "temp" counter -- when a state change happens (e.g. * port online), the temp count is set to the number of devices in the map. * Then, as each device gets "discovered", the temp counter is decremented * by one. When this count reaches zero we know that all of the devices * in the map have been discovered (or an error has occurred), so we can * then finish initialization -- which is done by this routine (well, this * and fcp-finish_tgt()) * * acquires and releases the global mutex * * called with the port mutex owned */ static void fcp_finish_init(struct fcp_port *pptr) { #ifdef DEBUG bzero(pptr->port_finish_stack, sizeof (pptr->port_finish_stack)); pptr->port_finish_depth = getpcstack(pptr->port_finish_stack, FCP_STACK_DEPTH); #endif /* DEBUG */ ASSERT(mutex_owned(&pptr->port_mutex)); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "finish_init:" " entering; ipkt count=%d", pptr->port_ipkt_cnt); if ((pptr->port_state & FCP_STATE_ONLINING) && !(pptr->port_state & (FCP_STATE_SUSPENDED | FCP_STATE_DETACHING | FCP_STATE_POWER_DOWN))) { pptr->port_state &= ~FCP_STATE_ONLINING; pptr->port_state |= FCP_STATE_ONLINE; } /* Wake up threads waiting on config done */ cv_broadcast(&pptr->port_config_cv); } /* * called from fcp_finish_init to create the LUNs for a target * * called with the port mutex owned */ static void fcp_create_luns(struct fcp_tgt *ptgt, int link_cnt, int tgt_cnt, int cause) { struct fcp_lun *plun; struct fcp_port *pptr; child_info_t *cip = NULL; ASSERT(ptgt != NULL); ASSERT(mutex_owned(&ptgt->tgt_mutex)); pptr = ptgt->tgt_port; ASSERT(pptr != NULL); /* scan all LUNs for this target */ for (plun = ptgt->tgt_lun; plun != NULL; plun = plun->lun_next) { if (plun->lun_state & FCP_LUN_OFFLINE) { continue; } if (plun->lun_state & FCP_LUN_MARK) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_create_luns: offlining marked LUN!"); fcp_offline_lun(plun, link_cnt, tgt_cnt, 1, 0); continue; } plun->lun_state &= ~FCP_LUN_BUSY; /* * There are conditions in which FCP_LUN_INIT flag is cleared * but we have a valid plun->lun_cip. To cover this case also * CLEAR_BUSY whenever we have a valid lun_cip. */ if (plun->lun_mpxio && plun->lun_cip && (!fcp_pass_to_hp(pptr, plun, plun->lun_cip, FCP_MPXIO_PATH_CLEAR_BUSY, link_cnt, tgt_cnt, 0, 0))) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_create_luns: enable lun %p failed!", plun); } if (plun->lun_state & FCP_LUN_INIT && !(plun->lun_state & FCP_LUN_CHANGED)) { continue; } if (cause == FCP_CAUSE_USER_CREATE) { continue; } FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_6, 0, "create_luns: passing ONLINE elem to HP thread"); /* * If lun has changed, prepare for offlining the old path. * Do not offline the old path right now, since it may be * still opened. */ if (plun->lun_cip && (plun->lun_state & FCP_LUN_CHANGED)) { fcp_prepare_offline_lun(plun, link_cnt, tgt_cnt); } /* pass an ONLINE element to the hotplug thread */ if (!fcp_pass_to_hp(pptr, plun, cip, FCP_ONLINE, link_cnt, tgt_cnt, NDI_ONLINE_ATTACH, 0)) { /* * We can not synchronous attach (i.e pass * NDI_ONLINE_ATTACH) here as we might be * coming from an interrupt or callback * thread. */ if (!fcp_pass_to_hp(pptr, plun, cip, FCP_ONLINE, link_cnt, tgt_cnt, 0, 0)) { fcp_log(CE_CONT, pptr->port_dip, "Can not ONLINE LUN; D_ID=%x, LUN=%x\n", plun->lun_tgt->tgt_d_id, plun->lun_num); } } } } /* * function to online/offline devices */ static int fcp_trigger_lun(struct fcp_lun *plun, child_info_t *cip, int old_mpxio, int online, int lcount, int tcount, int flags) { int rval = NDI_FAILURE; int circ; child_info_t *ccip; struct fcp_port *pptr = plun->lun_tgt->tgt_port; int is_mpxio = pptr->port_mpxio; dev_info_t *cdip, *pdip; char *devname; if ((old_mpxio != 0) && (plun->lun_mpxio != old_mpxio)) { /* * When this event gets serviced, lun_cip and lun_mpxio * has changed, so it should be invalidated now. */ FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_trigger_lun: lun_mpxio changed: " "plun: %p, cip: %p, what:%d", plun, cip, online); return (rval); } FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_trigger_lun: plun=%p target=%x lun=%d cip=%p what=%x " "flags=%x mpxio=%x\n", plun, LUN_TGT->tgt_d_id, plun->lun_num, cip, online, flags, plun->lun_mpxio); /* * lun_mpxio needs checking here because we can end up in a race * condition where this task has been dispatched while lun_mpxio is * set, but an earlier FCP_ONLINE task for the same LUN tried to * enable MPXIO for the LUN, but was unable to, and hence cleared * the flag. We rely on the serialization of the tasks here. We return * NDI_SUCCESS so any callers continue without reporting spurious * errors, and the still think we're an MPXIO LUN. */ if (online == FCP_MPXIO_PATH_CLEAR_BUSY || online == FCP_MPXIO_PATH_SET_BUSY) { if (plun->lun_mpxio) { rval = fcp_update_mpxio_path(plun, cip, online); } else { rval = NDI_SUCCESS; } return (rval); } /* * Explicit devfs_clean() due to ndi_devi_offline() not * executing devfs_clean() if parent lock is held. */ ASSERT(!servicing_interrupt()); if (online == FCP_OFFLINE) { if (plun->lun_mpxio == 0) { if (plun->lun_cip == cip) { cdip = DIP(plun->lun_cip); } else { cdip = DIP(cip); } } else if ((plun->lun_cip == cip) && plun->lun_cip) { cdip = mdi_pi_get_client(PIP(plun->lun_cip)); } else if ((plun->lun_cip != cip) && cip) { /* * This means a DTYPE/GUID change, we shall get the * dip of the old cip instead of the current lun_cip. */ cdip = mdi_pi_get_client(PIP(cip)); } if (cdip) { if (i_ddi_devi_attached(cdip)) { pdip = ddi_get_parent(cdip); devname = kmem_alloc(MAXNAMELEN + 1, KM_SLEEP); ndi_devi_enter(pdip, &circ); (void) ddi_deviname(cdip, devname); ndi_devi_exit(pdip, circ); /* * Release parent lock before calling * devfs_clean(). */ rval = devfs_clean(pdip, devname + 1, DV_CLEAN_FORCE); kmem_free(devname, MAXNAMELEN + 1); /* * Return if devfs_clean() fails for * non-MPXIO case. * For MPXIO case, another path could be * offlined. */ if (rval && plun->lun_mpxio == 0) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_trigger_lun: devfs_clean " "failed rval=%x dip=%p", rval, pdip); return (NDI_FAILURE); } } } } if (fc_ulp_busy_port(pptr->port_fp_handle) != 0) { return (NDI_FAILURE); } if (is_mpxio) { mdi_devi_enter(pptr->port_dip, &circ); } else { ndi_devi_enter(pptr->port_dip, &circ); } mutex_enter(&pptr->port_mutex); mutex_enter(&plun->lun_mutex); if (online == FCP_ONLINE) { ccip = fcp_get_cip(plun, cip, lcount, tcount); if (ccip == NULL) { goto fail; } } else { if (fcp_is_child_present(plun, cip) != FC_SUCCESS) { goto fail; } ccip = cip; } if (online == FCP_ONLINE) { rval = fcp_online_child(plun, ccip, lcount, tcount, flags, &circ); fc_ulp_log_device_event(pptr->port_fp_handle, FC_ULP_DEVICE_ONLINE); } else { rval = fcp_offline_child(plun, ccip, lcount, tcount, flags, &circ); fc_ulp_log_device_event(pptr->port_fp_handle, FC_ULP_DEVICE_OFFLINE); } fail: mutex_exit(&plun->lun_mutex); mutex_exit(&pptr->port_mutex); if (is_mpxio) { mdi_devi_exit(pptr->port_dip, circ); } else { ndi_devi_exit(pptr->port_dip, circ); } fc_ulp_idle_port(pptr->port_fp_handle); return (rval); } /* * take a target offline by taking all of its LUNs offline */ /*ARGSUSED*/ static int fcp_offline_target(struct fcp_port *pptr, struct fcp_tgt *ptgt, int link_cnt, int tgt_cnt, int nowait, int flags) { struct fcp_tgt_elem *elem; ASSERT(mutex_owned(&pptr->port_mutex)); ASSERT(mutex_owned(&ptgt->tgt_mutex)); ASSERT(!(ptgt->tgt_state & FCP_TGT_OFFLINE)); if (link_cnt != pptr->port_link_cnt || (tgt_cnt && tgt_cnt != ptgt->tgt_change_cnt)) { mutex_exit(&ptgt->tgt_mutex); FCP_TGT_TRACE(ptgt, tgt_cnt, FCP_TGT_TRACE_25); mutex_enter(&ptgt->tgt_mutex); return (0); } ptgt->tgt_pd_handle = NULL; mutex_exit(&ptgt->tgt_mutex); FCP_TGT_TRACE(ptgt, tgt_cnt, FCP_TGT_TRACE_26); mutex_enter(&ptgt->tgt_mutex); tgt_cnt = tgt_cnt ? tgt_cnt : ptgt->tgt_change_cnt; if (ptgt->tgt_tcap && (elem = kmem_zalloc(sizeof (*elem), KM_NOSLEEP)) != NULL) { elem->flags = flags; elem->time = fcp_watchdog_time; if (nowait == 0) { elem->time += fcp_offline_delay; } elem->ptgt = ptgt; elem->link_cnt = link_cnt; elem->tgt_cnt = tgt_cnt; elem->next = pptr->port_offline_tgts; pptr->port_offline_tgts = elem; } else { fcp_offline_target_now(pptr, ptgt, link_cnt, tgt_cnt, flags); } return (1); } static void fcp_offline_target_now(struct fcp_port *pptr, struct fcp_tgt *ptgt, int link_cnt, int tgt_cnt, int flags) { ASSERT(mutex_owned(&pptr->port_mutex)); ASSERT(mutex_owned(&ptgt->tgt_mutex)); fc_ulp_enable_relogin(pptr->port_fp_handle, &ptgt->tgt_port_wwn); ptgt->tgt_state = FCP_TGT_OFFLINE; ptgt->tgt_pd_handle = NULL; fcp_offline_tgt_luns(ptgt, link_cnt, tgt_cnt, flags); } static void fcp_offline_tgt_luns(struct fcp_tgt *ptgt, int link_cnt, int tgt_cnt, int flags) { struct fcp_lun *plun; ASSERT(mutex_owned(&ptgt->tgt_port->port_mutex)); ASSERT(mutex_owned(&ptgt->tgt_mutex)); for (plun = ptgt->tgt_lun; plun != NULL; plun = plun->lun_next) { if (!(plun->lun_state & FCP_LUN_OFFLINE)) { fcp_offline_lun(plun, link_cnt, tgt_cnt, 1, flags); } } } /* * take a LUN offline * * enters and leaves with the target mutex held, releasing it in the process * * allocates memory in non-sleep mode */ static void fcp_offline_lun(struct fcp_lun *plun, int link_cnt, int tgt_cnt, int nowait, int flags) { struct fcp_port *pptr = plun->lun_tgt->tgt_port; struct fcp_lun_elem *elem; ASSERT(plun != NULL); ASSERT(mutex_owned(&LUN_TGT->tgt_mutex)); if (nowait) { fcp_offline_lun_now(plun, link_cnt, tgt_cnt, flags); return; } if ((elem = kmem_zalloc(sizeof (*elem), KM_NOSLEEP)) != NULL) { elem->flags = flags; elem->time = fcp_watchdog_time; if (nowait == 0) { elem->time += fcp_offline_delay; } elem->plun = plun; elem->link_cnt = link_cnt; elem->tgt_cnt = plun->lun_tgt->tgt_change_cnt; elem->next = pptr->port_offline_luns; pptr->port_offline_luns = elem; } else { fcp_offline_lun_now(plun, link_cnt, tgt_cnt, flags); } } static void fcp_prepare_offline_lun(struct fcp_lun *plun, int link_cnt, int tgt_cnt) { struct fcp_pkt *head = NULL; ASSERT(mutex_owned(&LUN_TGT->tgt_mutex)); mutex_exit(&LUN_TGT->tgt_mutex); head = fcp_scan_commands(plun); if (head != NULL) { fcp_abort_commands(head, LUN_PORT); } mutex_enter(&LUN_TGT->tgt_mutex); if (plun->lun_cip && plun->lun_mpxio) { /* * Intimate MPxIO lun busy is cleared */ if (!fcp_pass_to_hp(LUN_PORT, plun, plun->lun_cip, FCP_MPXIO_PATH_CLEAR_BUSY, link_cnt, tgt_cnt, 0, 0)) { fcp_log(CE_NOTE, LUN_PORT->port_dip, "Can not ENABLE LUN; D_ID=%x, LUN=%x", LUN_TGT->tgt_d_id, plun->lun_num); } /* * Intimate MPxIO that the lun is now marked for offline */ mutex_exit(&LUN_TGT->tgt_mutex); (void) mdi_pi_disable_path(PIP(plun->lun_cip), DRIVER_DISABLE); mutex_enter(&LUN_TGT->tgt_mutex); } } static void fcp_offline_lun_now(struct fcp_lun *plun, int link_cnt, int tgt_cnt, int flags) { ASSERT(mutex_owned(&LUN_TGT->tgt_mutex)); mutex_exit(&LUN_TGT->tgt_mutex); fcp_update_offline_flags(plun); mutex_enter(&LUN_TGT->tgt_mutex); fcp_prepare_offline_lun(plun, link_cnt, tgt_cnt); FCP_TRACE(fcp_logq, LUN_PORT->port_instbuf, fcp_trace, FCP_BUF_LEVEL_4, 0, "offline_lun: passing OFFLINE elem to HP thread"); if (plun->lun_cip) { fcp_log(CE_NOTE, LUN_PORT->port_dip, "!offlining lun=%x (trace=%x), target=%x (trace=%x)", plun->lun_num, plun->lun_trace, LUN_TGT->tgt_d_id, LUN_TGT->tgt_trace); if (!fcp_pass_to_hp(LUN_PORT, plun, plun->lun_cip, FCP_OFFLINE, link_cnt, tgt_cnt, flags, 0)) { fcp_log(CE_CONT, LUN_PORT->port_dip, "Can not OFFLINE LUN; D_ID=%x, LUN=%x\n", LUN_TGT->tgt_d_id, plun->lun_num); } } } static void fcp_scan_offline_luns(struct fcp_port *pptr) { struct fcp_lun_elem *elem; struct fcp_lun_elem *prev; struct fcp_lun_elem *next; ASSERT(MUTEX_HELD(&pptr->port_mutex)); prev = NULL; elem = pptr->port_offline_luns; while (elem) { next = elem->next; if (elem->time <= fcp_watchdog_time) { int changed = 1; struct fcp_tgt *ptgt = elem->plun->lun_tgt; mutex_enter(&ptgt->tgt_mutex); if (pptr->port_link_cnt == elem->link_cnt && ptgt->tgt_change_cnt == elem->tgt_cnt) { changed = 0; } if (!changed && !(elem->plun->lun_state & FCP_TGT_OFFLINE)) { fcp_offline_lun_now(elem->plun, elem->link_cnt, elem->tgt_cnt, elem->flags); } mutex_exit(&ptgt->tgt_mutex); kmem_free(elem, sizeof (*elem)); if (prev) { prev->next = next; } else { pptr->port_offline_luns = next; } } else { prev = elem; } elem = next; } } static void fcp_scan_offline_tgts(struct fcp_port *pptr) { struct fcp_tgt_elem *elem; struct fcp_tgt_elem *prev; struct fcp_tgt_elem *next; ASSERT(MUTEX_HELD(&pptr->port_mutex)); prev = NULL; elem = pptr->port_offline_tgts; while (elem) { next = elem->next; if (elem->time <= fcp_watchdog_time) { int changed = 1; struct fcp_tgt *ptgt = elem->ptgt; if (ptgt->tgt_change_cnt == elem->tgt_cnt) { changed = 0; } mutex_enter(&ptgt->tgt_mutex); if (!changed && !(ptgt->tgt_state & FCP_TGT_OFFLINE)) { fcp_offline_target_now(pptr, ptgt, elem->link_cnt, elem->tgt_cnt, elem->flags); } mutex_exit(&ptgt->tgt_mutex); kmem_free(elem, sizeof (*elem)); if (prev) { prev->next = next; } else { pptr->port_offline_tgts = next; } } else { prev = elem; } elem = next; } } static void fcp_update_offline_flags(struct fcp_lun *plun) { struct fcp_port *pptr = LUN_PORT; ASSERT(plun != NULL); mutex_enter(&LUN_TGT->tgt_mutex); plun->lun_state |= FCP_LUN_OFFLINE; plun->lun_state &= ~(FCP_LUN_INIT | FCP_LUN_BUSY | FCP_LUN_MARK); mutex_enter(&plun->lun_mutex); if (plun->lun_cip && plun->lun_state & FCP_SCSI_LUN_TGT_INIT) { dev_info_t *cdip = NULL; mutex_exit(&LUN_TGT->tgt_mutex); if (plun->lun_mpxio == 0) { cdip = DIP(plun->lun_cip); } else if (plun->lun_cip) { cdip = mdi_pi_get_client(PIP(plun->lun_cip)); } mutex_exit(&plun->lun_mutex); if (cdip) { (void) ndi_event_retrieve_cookie( pptr->port_ndi_event_hdl, cdip, FCAL_REMOVE_EVENT, &fcp_remove_eid, NDI_EVENT_NOPASS); (void) ndi_event_run_callbacks( pptr->port_ndi_event_hdl, cdip, fcp_remove_eid, NULL); } } else { mutex_exit(&plun->lun_mutex); mutex_exit(&LUN_TGT->tgt_mutex); } } /* * Scan all of the command pkts for this port, moving pkts that * match our LUN onto our own list (headed by "head") */ static struct fcp_pkt * fcp_scan_commands(struct fcp_lun *plun) { struct fcp_port *pptr = LUN_PORT; struct fcp_pkt *cmd = NULL; /* pkt cmd ptr */ struct fcp_pkt *ncmd = NULL; /* next pkt ptr */ struct fcp_pkt *pcmd = NULL; /* the previous command */ struct fcp_pkt *head = NULL; /* head of our list */ struct fcp_pkt *tail = NULL; /* tail of our list */ int cmds_found = 0; mutex_enter(&pptr->port_pkt_mutex); for (cmd = pptr->port_pkt_head; cmd != NULL; cmd = ncmd) { struct fcp_lun *tlun = ADDR2LUN(&cmd->cmd_pkt->pkt_address); ncmd = cmd->cmd_next; /* set next command */ /* * if this pkt is for a different LUN or the * command is sent down, skip it. */ if (tlun != plun || cmd->cmd_state == FCP_PKT_ISSUED || (cmd->cmd_pkt->pkt_flags & FLAG_NOINTR)) { pcmd = cmd; continue; } cmds_found++; if (pcmd != NULL) { ASSERT(pptr->port_pkt_head != cmd); pcmd->cmd_next = cmd->cmd_next; } else { ASSERT(cmd == pptr->port_pkt_head); pptr->port_pkt_head = cmd->cmd_next; } if (cmd == pptr->port_pkt_tail) { pptr->port_pkt_tail = pcmd; if (pcmd) { pcmd->cmd_next = NULL; } } if (head == NULL) { head = tail = cmd; } else { ASSERT(tail != NULL); tail->cmd_next = cmd; tail = cmd; } cmd->cmd_next = NULL; } mutex_exit(&pptr->port_pkt_mutex); FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_8, 0, "scan commands: %d cmd(s) found", cmds_found); return (head); } /* * Abort all the commands in the command queue */ static void fcp_abort_commands(struct fcp_pkt *head, struct fcp_port *pptr) { struct fcp_pkt *cmd = NULL; /* pkt cmd ptr */ struct fcp_pkt *ncmd = NULL; /* next pkt ptr */ ASSERT(mutex_owned(&pptr->port_mutex)); /* scan through the pkts and invalid them */ for (cmd = head; cmd != NULL; cmd = ncmd) { struct scsi_pkt *pkt = cmd->cmd_pkt; ncmd = cmd->cmd_next; ASSERT(pkt != NULL); /* * The lun is going to be marked offline. Indicate * the target driver not to requeue or retry this command * as the device is going to be offlined pretty soon. */ pkt->pkt_reason = CMD_DEV_GONE; pkt->pkt_statistics = 0; pkt->pkt_state = 0; /* reset cmd flags/state */ cmd->cmd_flags &= ~CFLAG_IN_QUEUE; cmd->cmd_state = FCP_PKT_IDLE; /* * ensure we have a packet completion routine, * then call it. */ ASSERT(pkt->pkt_comp != NULL); mutex_exit(&pptr->port_mutex); fcp_post_callback(cmd); mutex_enter(&pptr->port_mutex); } } /* * the pkt_comp callback for command packets */ static void fcp_cmd_callback(fc_packet_t *fpkt) { struct fcp_pkt *cmd = (struct fcp_pkt *)fpkt->pkt_ulp_private; struct scsi_pkt *pkt = cmd->cmd_pkt; struct fcp_port *pptr = ADDR2FCP(&pkt->pkt_address); ASSERT(cmd->cmd_state != FCP_PKT_IDLE); if (cmd->cmd_state == FCP_PKT_IDLE) { cmn_err(CE_PANIC, "Packet already completed %p", (void *)cmd); } /* * Watch thread should be freeing the packet, ignore the pkt. */ if (cmd->cmd_state == FCP_PKT_ABORTING) { fcp_log(CE_CONT, pptr->port_dip, "!FCP: Pkt completed while aborting\n"); return; } cmd->cmd_state = FCP_PKT_IDLE; fcp_complete_pkt(fpkt); #ifdef DEBUG mutex_enter(&pptr->port_pkt_mutex); pptr->port_npkts--; mutex_exit(&pptr->port_pkt_mutex); #endif /* DEBUG */ fcp_post_callback(cmd); } static void fcp_complete_pkt(fc_packet_t *fpkt) { int error = 0; struct fcp_pkt *cmd = (struct fcp_pkt *) fpkt->pkt_ulp_private; struct scsi_pkt *pkt = cmd->cmd_pkt; struct fcp_port *pptr = ADDR2FCP(&pkt->pkt_address); struct fcp_lun *plun; struct fcp_tgt *ptgt; struct fcp_rsp *rsp; struct scsi_address save; #ifdef DEBUG save = pkt->pkt_address; #endif /* DEBUG */ rsp = (struct fcp_rsp *)cmd->cmd_fcp_rsp; if (fpkt->pkt_state == FC_PKT_SUCCESS) { if (pptr->port_fcp_dma != FC_NO_DVMA_SPACE) { FCP_CP_IN(fpkt->pkt_resp, rsp, fpkt->pkt_resp_acc, sizeof (struct fcp_rsp)); } pkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_GOT_STATUS; pkt->pkt_resid = 0; if (cmd->cmd_pkt->pkt_numcookies) { pkt->pkt_state |= STATE_XFERRED_DATA; if (fpkt->pkt_data_resid) { error++; } } if ((pkt->pkt_scbp != NULL) && ((*(pkt->pkt_scbp) = rsp->fcp_u.fcp_status.scsi_status) != STATUS_GOOD)) { /* * The next two checks make sure that if there * is no sense data or a valid response and * the command came back with check condition, * the command should be retried. */ if (!rsp->fcp_u.fcp_status.rsp_len_set && !rsp->fcp_u.fcp_status.sense_len_set) { pkt->pkt_state &= ~STATE_XFERRED_DATA; pkt->pkt_resid = cmd->cmd_dmacount; } } if ((error | rsp->fcp_u.i_fcp_status | rsp->fcp_resid) == 0) { return; } plun = ADDR2LUN(&pkt->pkt_address); ptgt = plun->lun_tgt; ASSERT(ptgt != NULL); /* * Update the transfer resid, if appropriate */ if (rsp->fcp_u.fcp_status.resid_over || rsp->fcp_u.fcp_status.resid_under) { pkt->pkt_resid = rsp->fcp_resid; } /* * First see if we got a FCP protocol error. */ if (rsp->fcp_u.fcp_status.rsp_len_set) { struct fcp_rsp_info *bep; bep = (struct fcp_rsp_info *)(cmd->cmd_fcp_rsp + sizeof (struct fcp_rsp)); if (fcp_validate_fcp_response(rsp, pptr) != FC_SUCCESS) { pkt->pkt_reason = CMD_CMPLT; *(pkt->pkt_scbp) = STATUS_CHECK; fcp_log(CE_WARN, pptr->port_dip, "!SCSI command to d_id=0x%x lun=0x%x" " failed, Bad FCP response values:" " rsvd1=%x, rsvd2=%x, sts-rsvd1=%x," " sts-rsvd2=%x, rsplen=%x, senselen=%x", ptgt->tgt_d_id, plun->lun_num, rsp->reserved_0, rsp->reserved_1, rsp->fcp_u.fcp_status.reserved_0, rsp->fcp_u.fcp_status.reserved_1, rsp->fcp_response_len, rsp->fcp_sense_len); return; } if (pptr->port_fcp_dma != FC_NO_DVMA_SPACE) { FCP_CP_IN(fpkt->pkt_resp + sizeof (struct fcp_rsp), bep, fpkt->pkt_resp_acc, sizeof (struct fcp_rsp_info)); } if (bep->rsp_code != FCP_NO_FAILURE) { child_info_t *cip; pkt->pkt_reason = CMD_TRAN_ERR; mutex_enter(&plun->lun_mutex); cip = plun->lun_cip; mutex_exit(&plun->lun_mutex); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "FCP response error on cmd=%p" " target=0x%x, cip=%p", cmd, ptgt->tgt_d_id, cip); } } /* * See if we got a SCSI error with sense data */ if (rsp->fcp_u.fcp_status.sense_len_set) { uchar_t rqlen; caddr_t sense_from; child_info_t *cip; timeout_id_t tid; struct scsi_arq_status *arq; struct scsi_extended_sense *sense_to; arq = (struct scsi_arq_status *)pkt->pkt_scbp; sense_to = &arq->sts_sensedata; rqlen = (uchar_t)min(rsp->fcp_sense_len, sizeof (struct scsi_extended_sense)); sense_from = (caddr_t)fpkt->pkt_resp + sizeof (struct fcp_rsp) + rsp->fcp_response_len; if (fcp_validate_fcp_response(rsp, pptr) != FC_SUCCESS) { pkt->pkt_reason = CMD_CMPLT; *(pkt->pkt_scbp) = STATUS_CHECK; fcp_log(CE_WARN, pptr->port_dip, "!SCSI command to d_id=0x%x lun=0x%x" " failed, Bad FCP response values:" " rsvd1=%x, rsvd2=%x, sts-rsvd1=%x," " sts-rsvd2=%x, rsplen=%x, senselen=%x", ptgt->tgt_d_id, plun->lun_num, rsp->reserved_0, rsp->reserved_1, rsp->fcp_u.fcp_status.reserved_0, rsp->fcp_u.fcp_status.reserved_1, rsp->fcp_response_len, rsp->fcp_sense_len); return; } /* * copy in sense information */ if (pptr->port_fcp_dma != FC_NO_DVMA_SPACE) { FCP_CP_IN(sense_from, sense_to, fpkt->pkt_resp_acc, rqlen); } else { bcopy(sense_from, sense_to, rqlen); } if ((FCP_SENSE_REPORTLUN_CHANGED(sense_to)) || (FCP_SENSE_NO_LUN(sense_to))) { mutex_enter(&ptgt->tgt_mutex); if (ptgt->tgt_tid == NULL) { /* * Kick off rediscovery */ tid = timeout(fcp_reconfigure_luns, (caddr_t)ptgt, drv_usectohz(1)); ptgt->tgt_tid = tid; ptgt->tgt_state |= FCP_TGT_BUSY; } mutex_exit(&ptgt->tgt_mutex); if (FCP_SENSE_REPORTLUN_CHANGED(sense_to)) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "!FCP: Report Lun Has Changed" " target=%x", ptgt->tgt_d_id); } else if (FCP_SENSE_NO_LUN(sense_to)) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "!FCP: LU Not Supported" " target=%x", ptgt->tgt_d_id); } } ASSERT(pkt->pkt_scbp != NULL); pkt->pkt_state |= STATE_ARQ_DONE; arq->sts_rqpkt_resid = SENSE_LENGTH - rqlen; *((uchar_t *)&arq->sts_rqpkt_status) = STATUS_GOOD; arq->sts_rqpkt_reason = 0; arq->sts_rqpkt_statistics = 0; arq->sts_rqpkt_state = STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_GOT_STATUS | STATE_ARQ_DONE | STATE_XFERRED_DATA; mutex_enter(&plun->lun_mutex); cip = plun->lun_cip; mutex_exit(&plun->lun_mutex); FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_8, 0, "SCSI Check condition on cmd=%p target=0x%x" " LUN=%p, cmd=%x SCSI status=%x, es key=%x" " ASC=%x ASCQ=%x", cmd, ptgt->tgt_d_id, cip, cmd->cmd_fcp_cmd.fcp_cdb[0], rsp->fcp_u.fcp_status.scsi_status, sense_to->es_key, sense_to->es_add_code, sense_to->es_qual_code); } } else { plun = ADDR2LUN(&pkt->pkt_address); ptgt = plun->lun_tgt; ASSERT(ptgt != NULL); /* * Work harder to translate errors into target driver * understandable ones. Note with despair that the target * drivers don't decode pkt_state and pkt_reason exhaustively * They resort to using the big hammer most often, which * may not get fixed in the life time of this driver. */ pkt->pkt_state = 0; pkt->pkt_statistics = 0; switch (fpkt->pkt_state) { case FC_PKT_TRAN_ERROR: switch (fpkt->pkt_reason) { case FC_REASON_OVERRUN: pkt->pkt_reason = CMD_CMD_OVR; pkt->pkt_statistics |= STAT_ABORTED; break; case FC_REASON_XCHG_BSY: { caddr_t ptr; pkt->pkt_reason = CMD_CMPLT; /* Lie */ ptr = (caddr_t)pkt->pkt_scbp; if (ptr) { *ptr = STATUS_BUSY; } break; } case FC_REASON_ABORTED: pkt->pkt_reason = CMD_TRAN_ERR; pkt->pkt_statistics |= STAT_ABORTED; break; case FC_REASON_ABORT_FAILED: pkt->pkt_reason = CMD_ABORT_FAIL; break; case FC_REASON_NO_SEQ_INIT: case FC_REASON_CRC_ERROR: pkt->pkt_reason = CMD_TRAN_ERR; pkt->pkt_statistics |= STAT_ABORTED; break; default: pkt->pkt_reason = CMD_TRAN_ERR; break; } break; case FC_PKT_PORT_OFFLINE: { dev_info_t *cdip = NULL; caddr_t ptr; if (fpkt->pkt_reason == FC_REASON_LOGIN_REQUIRED) { FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_8, 0, "SCSI cmd; LOGIN REQUIRED from FCA for %x", ptgt->tgt_d_id); } mutex_enter(&plun->lun_mutex); if (plun->lun_mpxio == 0) { cdip = DIP(plun->lun_cip); } else if (plun->lun_cip) { cdip = mdi_pi_get_client(PIP(plun->lun_cip)); } mutex_exit(&plun->lun_mutex); if (cdip) { (void) ndi_event_retrieve_cookie( pptr->port_ndi_event_hdl, cdip, FCAL_REMOVE_EVENT, &fcp_remove_eid, NDI_EVENT_NOPASS); (void) ndi_event_run_callbacks( pptr->port_ndi_event_hdl, cdip, fcp_remove_eid, NULL); } /* * If the link goes off-line for a lip, * this will cause a error to the ST SG * SGEN drivers. By setting BUSY we will * give the drivers the chance to retry * before it blows of the job. ST will * remember how many times it has retried. */ if ((plun->lun_type == DTYPE_SEQUENTIAL) || (plun->lun_type == DTYPE_CHANGER)) { pkt->pkt_reason = CMD_CMPLT; /* Lie */ ptr = (caddr_t)pkt->pkt_scbp; if (ptr) { *ptr = STATUS_BUSY; } } else { pkt->pkt_reason = CMD_TRAN_ERR; pkt->pkt_statistics |= STAT_BUS_RESET; } break; } case FC_PKT_TRAN_BSY: /* * Use the ssd Qfull handling here. */ *pkt->pkt_scbp = STATUS_INTERMEDIATE; pkt->pkt_state = STATE_GOT_BUS; break; case FC_PKT_TIMEOUT: pkt->pkt_reason = CMD_TIMEOUT; if (fpkt->pkt_reason == FC_REASON_ABORT_FAILED) { pkt->pkt_statistics |= STAT_TIMEOUT; } else { pkt->pkt_statistics |= STAT_ABORTED; } break; case FC_PKT_LOCAL_RJT: switch (fpkt->pkt_reason) { case FC_REASON_OFFLINE: { dev_info_t *cdip = NULL; mutex_enter(&plun->lun_mutex); if (plun->lun_mpxio == 0) { cdip = DIP(plun->lun_cip); } else if (plun->lun_cip) { cdip = mdi_pi_get_client( PIP(plun->lun_cip)); } mutex_exit(&plun->lun_mutex); if (cdip) { (void) ndi_event_retrieve_cookie( pptr->port_ndi_event_hdl, cdip, FCAL_REMOVE_EVENT, &fcp_remove_eid, NDI_EVENT_NOPASS); (void) ndi_event_run_callbacks( pptr->port_ndi_event_hdl, cdip, fcp_remove_eid, NULL); } pkt->pkt_reason = CMD_TRAN_ERR; pkt->pkt_statistics |= STAT_BUS_RESET; break; } case FC_REASON_NOMEM: case FC_REASON_QFULL: { caddr_t ptr; pkt->pkt_reason = CMD_CMPLT; /* Lie */ ptr = (caddr_t)pkt->pkt_scbp; if (ptr) { *ptr = STATUS_BUSY; } break; } case FC_REASON_DMA_ERROR: pkt->pkt_reason = CMD_DMA_DERR; pkt->pkt_statistics |= STAT_ABORTED; break; case FC_REASON_CRC_ERROR: case FC_REASON_UNDERRUN: { uchar_t status; /* * Work around for Bugid: 4240945. * IB on A5k doesn't set the Underrun bit * in the fcp status, when it is transferring * less than requested amount of data. Work * around the ses problem to keep luxadm * happy till ibfirmware is fixed. */ if (pptr->port_fcp_dma != FC_NO_DVMA_SPACE) { FCP_CP_IN(fpkt->pkt_resp, rsp, fpkt->pkt_resp_acc, sizeof (struct fcp_rsp)); } status = rsp->fcp_u.fcp_status.scsi_status; if (((plun->lun_type & DTYPE_MASK) == DTYPE_ESI) && (status == STATUS_GOOD)) { pkt->pkt_reason = CMD_CMPLT; *pkt->pkt_scbp = status; pkt->pkt_resid = 0; } else { pkt->pkt_reason = CMD_TRAN_ERR; pkt->pkt_statistics |= STAT_ABORTED; } break; } case FC_REASON_NO_CONNECTION: case FC_REASON_UNSUPPORTED: case FC_REASON_ILLEGAL_REQ: case FC_REASON_BAD_SID: case FC_REASON_DIAG_BUSY: case FC_REASON_FCAL_OPN_FAIL: case FC_REASON_BAD_XID: default: pkt->pkt_reason = CMD_TRAN_ERR; pkt->pkt_statistics |= STAT_ABORTED; break; } break; case FC_PKT_NPORT_RJT: case FC_PKT_FABRIC_RJT: case FC_PKT_NPORT_BSY: case FC_PKT_FABRIC_BSY: default: FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_8, 0, "FC Status 0x%x, reason 0x%x", fpkt->pkt_state, fpkt->pkt_reason); pkt->pkt_reason = CMD_TRAN_ERR; pkt->pkt_statistics |= STAT_ABORTED; break; } FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_9, 0, "!FC error on cmd=%p target=0x%x: pkt state=0x%x " " pkt reason=0x%x", cmd, ptgt->tgt_d_id, fpkt->pkt_state, fpkt->pkt_reason); } ASSERT(save.a_hba_tran == pkt->pkt_address.a_hba_tran); } static int fcp_validate_fcp_response(struct fcp_rsp *rsp, struct fcp_port *pptr) { if (rsp->reserved_0 || rsp->reserved_1 || rsp->fcp_u.fcp_status.reserved_0 || rsp->fcp_u.fcp_status.reserved_1) { /* * These reserved fields should ideally be zero. FCP-2 does say * that the recipient need not check for reserved fields to be * zero. If they are not zero, we will not make a fuss about it * - just log it (in debug to both trace buffer and messages * file and to trace buffer only in non-debug) and move on. * * Non-zero reserved fields were seen with minnows. * * qlc takes care of some of this but we cannot assume that all * FCAs will do so. */ FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "Got fcp response packet with non-zero reserved fields " "rsp->reserved_0:0x%x, rsp_reserved_1:0x%x, " "status.reserved_0:0x%x, status.reserved_1:0x%x", rsp->reserved_0, rsp->reserved_1, rsp->fcp_u.fcp_status.reserved_0, rsp->fcp_u.fcp_status.reserved_1); } if (rsp->fcp_u.fcp_status.rsp_len_set && (rsp->fcp_response_len > (FCP_MAX_RSP_IU_SIZE - sizeof (struct fcp_rsp)))) { return (FC_FAILURE); } if (rsp->fcp_u.fcp_status.sense_len_set && rsp->fcp_sense_len > (FCP_MAX_RSP_IU_SIZE - rsp->fcp_response_len - sizeof (struct fcp_rsp))) { return (FC_FAILURE); } return (FC_SUCCESS); } /* * This is called when there is a change the in device state. The case we're * handling here is, if the d_id s does not match, offline this tgt and online * a new tgt with the new d_id. called from fcp_handle_devices with * port_mutex held. */ static int fcp_device_changed(struct fcp_port *pptr, struct fcp_tgt *ptgt, fc_portmap_t *map_entry, int link_cnt, int tgt_cnt, int cause) { ASSERT(mutex_owned(&pptr->port_mutex)); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "Starting fcp_device_changed..."); /* * The two cases where the port_device_changed is called is * either it changes it's d_id or it's hard address. */ if ((ptgt->tgt_d_id != map_entry->map_did.port_id) || (FC_TOP_EXTERNAL(pptr->port_topology) && (ptgt->tgt_hard_addr != map_entry->map_hard_addr.hard_addr))) { /* offline this target */ mutex_enter(&ptgt->tgt_mutex); if (!(ptgt->tgt_state & FCP_TGT_OFFLINE)) { (void) fcp_offline_target(pptr, ptgt, link_cnt, 0, 1, NDI_DEVI_REMOVE); } mutex_exit(&ptgt->tgt_mutex); fcp_log(CE_NOTE, pptr->port_dip, "Change in target properties: Old D_ID=%x New D_ID=%x" " Old HA=%x New HA=%x", ptgt->tgt_d_id, map_entry->map_did.port_id, ptgt->tgt_hard_addr, map_entry->map_hard_addr.hard_addr); } return (fcp_handle_mapflags(pptr, ptgt, map_entry, link_cnt, tgt_cnt, cause)); } /* * Function: fcp_alloc_lun * * Description: Creates a new lun structure and adds it to the list * of luns of the target. * * Argument: ptgt Target the lun will belong to. * * Return Value: NULL Failed * Not NULL Succeeded * * Context: Kernel context */ static struct fcp_lun * fcp_alloc_lun(struct fcp_tgt *ptgt) { struct fcp_lun *plun; plun = kmem_zalloc(sizeof (struct fcp_lun), KM_NOSLEEP); if (plun != NULL) { /* * Initialize the mutex before putting in the target list * especially before releasing the target mutex. */ mutex_init(&plun->lun_mutex, NULL, MUTEX_DRIVER, NULL); plun->lun_tgt = ptgt; mutex_enter(&ptgt->tgt_mutex); plun->lun_next = ptgt->tgt_lun; ptgt->tgt_lun = plun; plun->lun_old_guid = NULL; plun->lun_old_guid_size = 0; mutex_exit(&ptgt->tgt_mutex); } return (plun); } /* * Function: fcp_dealloc_lun * * Description: Frees the LUN structure passed by the caller. * * Argument: plun LUN structure to free. * * Return Value: None * * Context: Kernel context. */ static void fcp_dealloc_lun(struct fcp_lun *plun) { mutex_enter(&plun->lun_mutex); if (plun->lun_cip) { fcp_remove_child(plun); } mutex_exit(&plun->lun_mutex); mutex_destroy(&plun->lun_mutex); if (plun->lun_guid) { kmem_free(plun->lun_guid, plun->lun_guid_size); } if (plun->lun_old_guid) { kmem_free(plun->lun_old_guid, plun->lun_old_guid_size); } kmem_free(plun, sizeof (*plun)); } /* * Function: fcp_alloc_tgt * * Description: Creates a new target structure and adds it to the port * hash list. * * Argument: pptr fcp port structure * *map_entry entry describing the target to create * link_cnt Link state change counter * * Return Value: NULL Failed * Not NULL Succeeded * * Context: Kernel context. */ static struct fcp_tgt * fcp_alloc_tgt(struct fcp_port *pptr, fc_portmap_t *map_entry, int link_cnt) { int hash; uchar_t *wwn; struct fcp_tgt *ptgt; ptgt = kmem_zalloc(sizeof (*ptgt), KM_NOSLEEP); if (ptgt != NULL) { mutex_enter(&pptr->port_mutex); if (link_cnt != pptr->port_link_cnt) { /* * oh oh -- another link reset * in progress -- give up */ mutex_exit(&pptr->port_mutex); kmem_free(ptgt, sizeof (*ptgt)); ptgt = NULL; } else { /* * initialize the mutex before putting in the port * wwn list, especially before releasing the port * mutex. */ mutex_init(&ptgt->tgt_mutex, NULL, MUTEX_DRIVER, NULL); /* add new target entry to the port's hash list */ wwn = (uchar_t *)&map_entry->map_pwwn; hash = FCP_HASH(wwn); ptgt->tgt_next = pptr->port_tgt_hash_table[hash]; pptr->port_tgt_hash_table[hash] = ptgt; /* save cross-ptr */ ptgt->tgt_port = pptr; ptgt->tgt_change_cnt = 1; /* initialize the target manual_config_only flag */ if (fcp_enable_auto_configuration) { ptgt->tgt_manual_config_only = 0; } else { ptgt->tgt_manual_config_only = 1; } mutex_exit(&pptr->port_mutex); } } return (ptgt); } /* * Function: fcp_dealloc_tgt * * Description: Frees the target structure passed by the caller. * * Argument: ptgt Target structure to free. * * Return Value: None * * Context: Kernel context. */ static void fcp_dealloc_tgt(struct fcp_tgt *ptgt) { mutex_destroy(&ptgt->tgt_mutex); kmem_free(ptgt, sizeof (*ptgt)); } /* * Handle STATUS_QFULL and STATUS_BUSY by performing delayed retry * * Device discovery commands will not be retried for-ever as * this will have repercussions on other devices that need to * be submitted to the hotplug thread. After a quick glance * at the SCSI-3 spec, it was found that the spec doesn't * mandate a forever retry, rather recommends a delayed retry. * * Since Photon IB is single threaded, STATUS_BUSY is common * in a 4+initiator environment. Make sure the total time * spent on retries (including command timeout) does not * 60 seconds */ static void fcp_queue_ipkt(struct fcp_port *pptr, fc_packet_t *fpkt) { struct fcp_ipkt *icmd = (struct fcp_ipkt *)fpkt->pkt_ulp_private; struct fcp_tgt *ptgt = icmd->ipkt_tgt; mutex_enter(&pptr->port_mutex); mutex_enter(&ptgt->tgt_mutex); if (FCP_STATE_CHANGED(pptr, ptgt, icmd)) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_queue_ipkt,1:state change occured" " for D_ID=0x%x", ptgt->tgt_d_id); mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); fcp_icmd_free(pptr, icmd); return; } mutex_exit(&ptgt->tgt_mutex); icmd->ipkt_restart = fcp_watchdog_time + icmd->ipkt_retries++; if (pptr->port_ipkt_list != NULL) { /* add pkt to front of doubly-linked list */ pptr->port_ipkt_list->ipkt_prev = icmd; icmd->ipkt_next = pptr->port_ipkt_list; pptr->port_ipkt_list = icmd; icmd->ipkt_prev = NULL; } else { /* this is the first/only pkt on the list */ pptr->port_ipkt_list = icmd; icmd->ipkt_next = NULL; icmd->ipkt_prev = NULL; } mutex_exit(&pptr->port_mutex); } /* * Function: fcp_transport * * Description: This function submits the Fibre Channel packet to the transort * layer by calling fc_ulp_transport(). If fc_ulp_transport() * fails the submission, the treatment depends on the value of * the variable internal. * * Argument: port_handle fp/fctl port handle. * *fpkt Packet to submit to the transport layer. * internal Not zero when it's an internal packet. * * Return Value: FC_TRAN_BUSY * FC_STATEC_BUSY * FC_OFFLINE * FC_LOGINREQ * FC_DEVICE_BUSY * FC_SUCCESS */ static int fcp_transport(opaque_t port_handle, fc_packet_t *fpkt, int internal) { int rval; rval = fc_ulp_transport(port_handle, fpkt); if (rval == FC_SUCCESS) { return (rval); } /* * LUN isn't marked BUSY or OFFLINE, so we got here to transport * a command, if the underlying modules see that there is a state * change, or if a port is OFFLINE, that means, that state change * hasn't reached FCP yet, so re-queue the command for deferred * submission. */ if ((rval == FC_STATEC_BUSY) || (rval == FC_OFFLINE) || (rval == FC_LOGINREQ) || (rval == FC_DEVICE_BUSY) || (rval == FC_DEVICE_BUSY_NEW_RSCN) || (rval == FC_TRAN_BUSY)) { /* * Defer packet re-submission. Life hang is possible on * internal commands if the port driver sends FC_STATEC_BUSY * for ever, but that shouldn't happen in a good environment. * Limiting re-transport for internal commands is probably a * good idea.. * A race condition can happen when a port sees barrage of * link transitions offline to online. If the FCTL has * returned FC_STATEC_BUSY or FC_OFFLINE then none of the * internal commands should be queued to do the discovery. * The race condition is when an online comes and FCP starts * its internal discovery and the link goes offline. It is * possible that the statec_callback has not reached FCP * and FCP is carrying on with its internal discovery. * FC_STATEC_BUSY or FC_OFFLINE will be the first indication * that the link has gone offline. At this point FCP should * drop all the internal commands and wait for the * statec_callback. It will be facilitated by incrementing * port_link_cnt. * * For external commands, the (FC)pkt_timeout is decremented * by the QUEUE Delay added by our driver, Care is taken to * ensure that it doesn't become zero (zero means no timeout) * If the time expires right inside driver queue itself, * the watch thread will return it to the original caller * indicating that the command has timed-out. */ if (internal) { char *op; struct fcp_ipkt *icmd; icmd = (struct fcp_ipkt *)fpkt->pkt_ulp_private; switch (icmd->ipkt_opcode) { case SCMD_REPORT_LUN: op = "REPORT LUN"; break; case SCMD_INQUIRY: op = "INQUIRY"; break; case SCMD_INQUIRY_PAGE83: op = "INQUIRY-83"; break; default: op = "Internal SCSI COMMAND"; break; } if (fcp_handle_ipkt_errors(icmd->ipkt_port, icmd->ipkt_tgt, icmd, rval, op) == DDI_SUCCESS) { rval = FC_SUCCESS; } } else { struct fcp_pkt *cmd; struct fcp_port *pptr; cmd = (struct fcp_pkt *)fpkt->pkt_ulp_private; cmd->cmd_state = FCP_PKT_IDLE; pptr = ADDR2FCP(&cmd->cmd_pkt->pkt_address); if (cmd->cmd_pkt->pkt_flags & FLAG_NOQUEUE) { FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_9, 0, "fcp_transport: xport busy for pkt %p", cmd->cmd_pkt); rval = FC_TRAN_BUSY; } else { fcp_queue_pkt(pptr, cmd); rval = FC_SUCCESS; } } } return (rval); } /*VARARGS3*/ static void fcp_log(int level, dev_info_t *dip, const char *fmt, ...) { char buf[256]; va_list ap; if (dip == NULL) { dip = fcp_global_dip; } va_start(ap, fmt); (void) vsprintf(buf, fmt, ap); va_end(ap); scsi_log(dip, "fcp", level, buf); } /* * This function retries NS registry of FC4 type. * It assumes that fcp_mutex is held. * The function does nothing if topology is not fabric * So, the topology has to be set before this function can be called */ static void fcp_retry_ns_registry(struct fcp_port *pptr, uint32_t s_id) { int rval; ASSERT(MUTEX_HELD(&pptr->port_mutex)); if (((pptr->port_state & FCP_STATE_NS_REG_FAILED) == 0) || ((pptr->port_topology != FC_TOP_FABRIC) && (pptr->port_topology != FC_TOP_PUBLIC_LOOP))) { if (pptr->port_state & FCP_STATE_NS_REG_FAILED) { pptr->port_state &= ~FCP_STATE_NS_REG_FAILED; } return; } mutex_exit(&pptr->port_mutex); rval = fcp_do_ns_registry(pptr, s_id); mutex_enter(&pptr->port_mutex); if (rval == 0) { /* Registry successful. Reset flag */ pptr->port_state &= ~(FCP_STATE_NS_REG_FAILED); } } /* * This function registers the ULP with the switch by calling transport i/f */ static int fcp_do_ns_registry(struct fcp_port *pptr, uint32_t s_id) { fc_ns_cmd_t ns_cmd; ns_rfc_type_t rfc; uint32_t types[8]; /* * Prepare the Name server structure to * register with the transport in case of * Fabric configuration. */ bzero(&rfc, sizeof (rfc)); bzero(types, sizeof (types)); types[FC4_TYPE_WORD_POS(FC_TYPE_SCSI_FCP)] = (1 << FC4_TYPE_BIT_POS(FC_TYPE_SCSI_FCP)); rfc.rfc_port_id.port_id = s_id; bcopy(types, rfc.rfc_types, sizeof (types)); ns_cmd.ns_flags = 0; ns_cmd.ns_cmd = NS_RFT_ID; ns_cmd.ns_req_len = sizeof (rfc); ns_cmd.ns_req_payload = (caddr_t)&rfc; ns_cmd.ns_resp_len = 0; ns_cmd.ns_resp_payload = NULL; /* * Perform the Name Server Registration for SCSI_FCP FC4 Type. */ if (fc_ulp_port_ns(pptr->port_fp_handle, NULL, &ns_cmd)) { fcp_log(CE_WARN, pptr->port_dip, "!ns_registry: failed name server registration"); return (1); } return (0); } /* * Function: fcp_handle_port_attach * * Description: This function is called from fcp_port_attach() to attach a * new port. This routine does the following: * * 1) Allocates an fcp_port structure and initializes it. * 2) Tries to register the new FC-4 (FCP) capablity with the name * server. * 3) Kicks off the enumeration of the targets/luns visible * through this new port. That is done by calling * fcp_statec_callback() if the port is online. * * Argument: ulph fp/fctl port handle. * *pinfo Port information. * s_id Port ID. * instance Device instance number for the local port * (returned by ddi_get_instance()). * * Return Value: DDI_SUCCESS * DDI_FAILURE * * Context: User and Kernel context. */ /*ARGSUSED*/ int fcp_handle_port_attach(opaque_t ulph, fc_ulp_port_info_t *pinfo, uint32_t s_id, int instance) { int res = DDI_FAILURE; scsi_hba_tran_t *tran; int mutex_initted = FALSE; int hba_attached = FALSE; int soft_state_linked = FALSE; int event_bind = FALSE; struct fcp_port *pptr; fc_portmap_t *tmp_list = NULL; uint32_t max_cnt, alloc_cnt; uchar_t *boot_wwn = NULL; uint_t nbytes; int manual_cfg; /* * this port instance attaching for the first time (or after * being detached before) */ FCP_TRACE(fcp_logq, "fcp", fcp_trace, FCP_BUF_LEVEL_3, 0, "port attach: for port %d", instance); if (ddi_soft_state_zalloc(fcp_softstate, instance) != DDI_SUCCESS) { cmn_err(CE_WARN, "fcp: Softstate struct alloc failed" "parent dip: %p; instance: %d", (void *)pinfo->port_dip, instance); return (res); } if ((pptr = ddi_get_soft_state(fcp_softstate, instance)) == NULL) { /* this shouldn't happen */ ddi_soft_state_free(fcp_softstate, instance); cmn_err(CE_WARN, "fcp: bad soft state"); return (res); } (void) sprintf(pptr->port_instbuf, "fcp(%d)", instance); /* * Make a copy of ulp_port_info as fctl allocates * a temp struct. */ (void) fcp_cp_pinfo(pptr, pinfo); /* * Check for manual_configuration_only property. * Enable manual configurtion if the property is * set to 1, otherwise disable manual configuration. */ if ((manual_cfg = ddi_prop_get_int(DDI_DEV_T_ANY, pptr->port_dip, DDI_PROP_NOTPROM | DDI_PROP_DONTPASS, MANUAL_CFG_ONLY, -1)) != -1) { if (manual_cfg == 1) { char *pathname; pathname = kmem_zalloc(MAXPATHLEN, KM_SLEEP); (void) ddi_pathname(pptr->port_dip, pathname); cmn_err(CE_NOTE, "%s (%s%d) %s is enabled via %s.conf.", pathname, ddi_driver_name(pptr->port_dip), ddi_get_instance(pptr->port_dip), MANUAL_CFG_ONLY, ddi_driver_name(pptr->port_dip)); fcp_enable_auto_configuration = 0; kmem_free(pathname, MAXPATHLEN); } } _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(pptr->port_link_cnt)) pptr->port_link_cnt = 1; _NOTE(NOW_VISIBLE_TO_OTHER_THREADS(pptr->port_link_cnt)) pptr->port_id = s_id; pptr->port_instance = instance; _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(pptr->port_state)) pptr->port_state = FCP_STATE_INIT; _NOTE(NOW_VISIBLE_TO_OTHER_THREADS(pptr->port_state)) pptr->port_dmacookie_sz = (pptr->port_data_dma_attr.dma_attr_sgllen * sizeof (ddi_dma_cookie_t)); /* * The two mutexes of fcp_port are initialized. The variable * mutex_initted is incremented to remember that fact. That variable * is checked when the routine fails and the mutexes have to be * destroyed. */ mutex_init(&pptr->port_mutex, NULL, MUTEX_DRIVER, NULL); mutex_init(&pptr->port_pkt_mutex, NULL, MUTEX_DRIVER, NULL); mutex_initted++; /* * The SCSI tran structure is allocate and initialized now. */ if ((tran = scsi_hba_tran_alloc(pptr->port_dip, 0)) == NULL) { fcp_log(CE_WARN, pptr->port_dip, "!fcp%d: scsi_hba_tran_alloc failed", instance); goto fail; } /* link in the transport structure then fill it in */ pptr->port_tran = tran; tran->tran_hba_private = pptr; tran->tran_tgt_init = fcp_scsi_tgt_init; tran->tran_tgt_probe = NULL; tran->tran_tgt_free = fcp_scsi_tgt_free; tran->tran_start = fcp_scsi_start; tran->tran_reset = fcp_scsi_reset; tran->tran_abort = fcp_scsi_abort; tran->tran_getcap = fcp_scsi_getcap; tran->tran_setcap = fcp_scsi_setcap; tran->tran_init_pkt = NULL; tran->tran_destroy_pkt = NULL; tran->tran_dmafree = NULL; tran->tran_sync_pkt = NULL; tran->tran_reset_notify = fcp_scsi_reset_notify; tran->tran_get_bus_addr = fcp_scsi_get_bus_addr; tran->tran_get_name = fcp_scsi_get_name; tran->tran_clear_aca = NULL; tran->tran_clear_task_set = NULL; tran->tran_terminate_task = NULL; tran->tran_get_eventcookie = fcp_scsi_bus_get_eventcookie; tran->tran_add_eventcall = fcp_scsi_bus_add_eventcall; tran->tran_remove_eventcall = fcp_scsi_bus_remove_eventcall; tran->tran_post_event = fcp_scsi_bus_post_event; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_bus_reset = NULL; tran->tran_bus_config = fcp_scsi_bus_config; tran->tran_bus_unconfig = fcp_scsi_bus_unconfig; tran->tran_bus_power = NULL; tran->tran_interconnect_type = INTERCONNECT_FABRIC; tran->tran_pkt_constructor = fcp_kmem_cache_constructor; tran->tran_pkt_destructor = fcp_kmem_cache_destructor; tran->tran_setup_pkt = fcp_pkt_setup; tran->tran_teardown_pkt = fcp_pkt_teardown; tran->tran_hba_len = pptr->port_priv_pkt_len + sizeof (struct fcp_pkt) + pptr->port_dmacookie_sz; /* * Allocate an ndi event handle */ pptr->port_ndi_event_defs = (ndi_event_definition_t *) kmem_zalloc(sizeof (fcp_ndi_event_defs), KM_SLEEP); bcopy(fcp_ndi_event_defs, pptr->port_ndi_event_defs, sizeof (fcp_ndi_event_defs)); (void) ndi_event_alloc_hdl(pptr->port_dip, NULL, &pptr->port_ndi_event_hdl, NDI_SLEEP); pptr->port_ndi_events.ndi_events_version = NDI_EVENTS_REV1; pptr->port_ndi_events.ndi_n_events = FCP_N_NDI_EVENTS; pptr->port_ndi_events.ndi_event_defs = pptr->port_ndi_event_defs; if (DEVI_IS_ATTACHING(pptr->port_dip) && (ndi_event_bind_set(pptr->port_ndi_event_hdl, &pptr->port_ndi_events, NDI_SLEEP) != NDI_SUCCESS)) { goto fail; } event_bind++; /* Checked in fail case */ if (scsi_hba_attach_setup(pptr->port_dip, &pptr->port_data_dma_attr, tran, SCSI_HBA_ADDR_COMPLEX | SCSI_HBA_TRAN_SCB) != DDI_SUCCESS) { fcp_log(CE_WARN, pptr->port_dip, "!fcp%d: scsi_hba_attach_setup failed", instance); goto fail; } hba_attached++; /* Checked in fail case */ pptr->port_mpxio = 0; if (mdi_phci_register(MDI_HCI_CLASS_SCSI, pptr->port_dip, 0) == MDI_SUCCESS) { pptr->port_mpxio++; } /* * The following code is putting the new port structure in the global * list of ports and, if it is the first port to attach, it start the * fcp_watchdog_tick. * * Why put this new port in the global before we are done attaching it? * We are actually making the structure globally known before we are * done attaching it. The reason for that is: because of the code that * follows. At this point the resources to handle the port are * allocated. This function is now going to do the following: * * 1) It is going to try to register with the name server advertizing * the new FCP capability of the port. * 2) It is going to play the role of the fp/fctl layer by building * a list of worlwide names reachable through this port and call * itself on fcp_statec_callback(). That requires the port to * be part of the global list. */ mutex_enter(&fcp_global_mutex); if (fcp_port_head == NULL) { fcp_read_blacklist(pinfo->port_dip, &fcp_lun_blacklist); } pptr->port_next = fcp_port_head; fcp_port_head = pptr; soft_state_linked++; if (fcp_watchdog_init++ == 0) { fcp_watchdog_tick = fcp_watchdog_timeout * drv_usectohz(1000000); fcp_watchdog_id = timeout(fcp_watch, NULL, fcp_watchdog_tick); } mutex_exit(&fcp_global_mutex); /* * Here an attempt is made to register with the name server, the new * FCP capability. That is done using an RTF_ID to the name server. * It is done synchronously. The function fcp_do_ns_registry() * doesn't return till the name server responded. * On failures, just ignore it for now and it will get retried during * state change callbacks. We'll set a flag to show this failure */ if (fcp_do_ns_registry(pptr, s_id)) { mutex_enter(&pptr->port_mutex); pptr->port_state |= FCP_STATE_NS_REG_FAILED; mutex_exit(&pptr->port_mutex); } else { mutex_enter(&pptr->port_mutex); pptr->port_state &= ~(FCP_STATE_NS_REG_FAILED); mutex_exit(&pptr->port_mutex); } /* * Lookup for boot WWN property */ if (modrootloaded != 1) { if ((ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, ddi_get_parent(pinfo->port_dip), DDI_PROP_DONTPASS, OBP_BOOT_WWN, &boot_wwn, &nbytes) == DDI_PROP_SUCCESS) && (nbytes == FC_WWN_SIZE)) { bcopy(boot_wwn, pptr->port_boot_wwn, FC_WWN_SIZE); } if (boot_wwn) { ddi_prop_free(boot_wwn); } } /* * Handle various topologies and link states. */ switch (FC_PORT_STATE_MASK(pptr->port_phys_state)) { case FC_STATE_OFFLINE: /* * we're attaching a port where the link is offline * * Wait for ONLINE, at which time a state * change will cause a statec_callback * * in the mean time, do not do anything */ res = DDI_SUCCESS; pptr->port_state |= FCP_STATE_OFFLINE; break; case FC_STATE_ONLINE: { if (pptr->port_topology == FC_TOP_UNKNOWN) { (void) fcp_linkreset(pptr, NULL, KM_NOSLEEP); res = DDI_SUCCESS; break; } /* * discover devices and create nodes (a private * loop or point-to-point) */ ASSERT(pptr->port_topology != FC_TOP_UNKNOWN); /* * At this point we are going to build a list of all the ports * that can be reached through this local port. It looks like * we cannot handle more than FCP_MAX_DEVICES per local port * (128). */ if ((tmp_list = (fc_portmap_t *)kmem_zalloc( sizeof (fc_portmap_t) * FCP_MAX_DEVICES, KM_NOSLEEP)) == NULL) { fcp_log(CE_WARN, pptr->port_dip, "!fcp%d: failed to allocate portmap", instance); goto fail; } /* * fc_ulp_getportmap() is going to provide us with the list of * remote ports in the buffer we just allocated. The way the * list is going to be retrieved depends on the topology. * However, if we are connected to a Fabric, a name server * request may be sent to get the list of FCP capable ports. * It should be noted that is the case the request is * synchronous. This means we are stuck here till the name * server replies. A lot of things can change during that time * and including, may be, being called on * fcp_statec_callback() for different reasons. I'm not sure * the code can handle that. */ max_cnt = FCP_MAX_DEVICES; alloc_cnt = FCP_MAX_DEVICES; if ((res = fc_ulp_getportmap(pptr->port_fp_handle, &tmp_list, &max_cnt, FC_ULP_PLOGI_PRESERVE)) != FC_SUCCESS) { caddr_t msg; (void) fc_ulp_error(res, &msg); /* * this just means the transport is * busy perhaps building a portmap so, * for now, succeed this port attach * when the transport has a new map, * it'll send us a state change then */ fcp_log(CE_WARN, pptr->port_dip, "!failed to get port map : %s", msg); res = DDI_SUCCESS; break; /* go return result */ } if (max_cnt > alloc_cnt) { alloc_cnt = max_cnt; } /* * We are now going to call fcp_statec_callback() ourselves. * By issuing this call we are trying to kick off the enumera- * tion process. */ /* * let the state change callback do the SCSI device * discovery and create the devinfos */ fcp_statec_callback(ulph, pptr->port_fp_handle, pptr->port_phys_state, pptr->port_topology, tmp_list, max_cnt, pptr->port_id); res = DDI_SUCCESS; break; } default: /* unknown port state */ fcp_log(CE_WARN, pptr->port_dip, "!fcp%d: invalid port state at attach=0x%x", instance, pptr->port_phys_state); mutex_enter(&pptr->port_mutex); pptr->port_phys_state = FCP_STATE_OFFLINE; mutex_exit(&pptr->port_mutex); res = DDI_SUCCESS; break; } /* free temp list if used */ if (tmp_list != NULL) { kmem_free(tmp_list, sizeof (fc_portmap_t) * alloc_cnt); } /* note the attach time */ pptr->port_attach_time = lbolt64; /* all done */ return (res); /* a failure we have to clean up after */ fail: fcp_log(CE_WARN, pptr->port_dip, "!failed to attach to port"); if (soft_state_linked) { /* remove this fcp_port from the linked list */ (void) fcp_soft_state_unlink(pptr); } /* unbind and free event set */ if (pptr->port_ndi_event_hdl) { if (event_bind) { (void) ndi_event_unbind_set(pptr->port_ndi_event_hdl, &pptr->port_ndi_events, NDI_SLEEP); } (void) ndi_event_free_hdl(pptr->port_ndi_event_hdl); } if (pptr->port_ndi_event_defs) { (void) kmem_free(pptr->port_ndi_event_defs, sizeof (fcp_ndi_event_defs)); } /* * Clean up mpxio stuff */ if (pptr->port_mpxio) { (void) mdi_phci_unregister(pptr->port_dip, 0); pptr->port_mpxio--; } /* undo SCSI HBA setup */ if (hba_attached) { (void) scsi_hba_detach(pptr->port_dip); } if (pptr->port_tran != NULL) { scsi_hba_tran_free(pptr->port_tran); } mutex_enter(&fcp_global_mutex); /* * We check soft_state_linked, because it is incremented right before * we call increment fcp_watchdog_init. Therefore, we know if * soft_state_linked is still FALSE, we do not want to decrement * fcp_watchdog_init or possibly call untimeout. */ if (soft_state_linked) { if (--fcp_watchdog_init == 0) { timeout_id_t tid = fcp_watchdog_id; mutex_exit(&fcp_global_mutex); (void) untimeout(tid); } else { mutex_exit(&fcp_global_mutex); } } else { mutex_exit(&fcp_global_mutex); } if (mutex_initted) { mutex_destroy(&pptr->port_mutex); mutex_destroy(&pptr->port_pkt_mutex); } if (tmp_list != NULL) { kmem_free(tmp_list, sizeof (fc_portmap_t) * alloc_cnt); } /* this makes pptr invalid */ ddi_soft_state_free(fcp_softstate, instance); return (DDI_FAILURE); } static int fcp_handle_port_detach(struct fcp_port *pptr, int flag, int instance) { int count = 0; mutex_enter(&pptr->port_mutex); /* * if the port is powered down or suspended, nothing else * to do; just return. */ if (flag != FCP_STATE_DETACHING) { if (pptr->port_state & (FCP_STATE_POWER_DOWN | FCP_STATE_SUSPENDED)) { pptr->port_state |= flag; mutex_exit(&pptr->port_mutex); return (FC_SUCCESS); } } if (pptr->port_state & FCP_STATE_IN_MDI) { mutex_exit(&pptr->port_mutex); return (FC_FAILURE); } FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_handle_port_detach: port is detaching"); pptr->port_state |= flag; /* * Wait for any ongoing reconfig/ipkt to complete, that * ensures the freeing to targets/luns is safe. * No more ref to this port should happen from statec/ioctl * after that as it was removed from the global port list. */ while (pptr->port_tmp_cnt || pptr->port_ipkt_cnt || (pptr->port_state & FCP_STATE_IN_WATCHDOG)) { /* * Let's give sufficient time for reconfig/ipkt * to complete. */ if (count++ >= FCP_ICMD_DEADLINE) { break; } mutex_exit(&pptr->port_mutex); delay(drv_usectohz(1000000)); mutex_enter(&pptr->port_mutex); } /* * if the driver is still busy then fail to * suspend/power down. */ if (pptr->port_tmp_cnt || pptr->port_ipkt_cnt || (pptr->port_state & FCP_STATE_IN_WATCHDOG)) { pptr->port_state &= ~flag; mutex_exit(&pptr->port_mutex); return (FC_FAILURE); } if (flag == FCP_STATE_DETACHING) { pptr = fcp_soft_state_unlink(pptr); ASSERT(pptr != NULL); } pptr->port_link_cnt++; pptr->port_state |= FCP_STATE_OFFLINE; pptr->port_state &= ~(FCP_STATE_ONLINING | FCP_STATE_ONLINE); fcp_update_state(pptr, (FCP_LUN_BUSY | FCP_LUN_MARK), FCP_CAUSE_LINK_DOWN); mutex_exit(&pptr->port_mutex); /* kill watch dog timer if we're the last */ mutex_enter(&fcp_global_mutex); if (--fcp_watchdog_init == 0) { timeout_id_t tid = fcp_watchdog_id; mutex_exit(&fcp_global_mutex); (void) untimeout(tid); } else { mutex_exit(&fcp_global_mutex); } /* clean up the port structures */ if (flag == FCP_STATE_DETACHING) { fcp_cleanup_port(pptr, instance); } return (FC_SUCCESS); } static void fcp_cleanup_port(struct fcp_port *pptr, int instance) { ASSERT(pptr != NULL); /* unbind and free event set */ if (pptr->port_ndi_event_hdl) { (void) ndi_event_unbind_set(pptr->port_ndi_event_hdl, &pptr->port_ndi_events, NDI_SLEEP); (void) ndi_event_free_hdl(pptr->port_ndi_event_hdl); } if (pptr->port_ndi_event_defs) { (void) kmem_free(pptr->port_ndi_event_defs, sizeof (fcp_ndi_event_defs)); } /* free the lun/target structures and devinfos */ fcp_free_targets(pptr); /* * Clean up mpxio stuff */ if (pptr->port_mpxio) { (void) mdi_phci_unregister(pptr->port_dip, 0); pptr->port_mpxio--; } /* clean up SCSA stuff */ (void) scsi_hba_detach(pptr->port_dip); if (pptr->port_tran != NULL) { scsi_hba_tran_free(pptr->port_tran); } #ifdef KSTATS_CODE /* clean up kstats */ if (pptr->fcp_ksp != NULL) { kstat_delete(pptr->fcp_ksp); } #endif /* clean up soft state mutexes/condition variables */ mutex_destroy(&pptr->port_mutex); mutex_destroy(&pptr->port_pkt_mutex); /* all done with soft state */ ddi_soft_state_free(fcp_softstate, instance); } /* * Function: fcp_kmem_cache_constructor * * Description: This function allocates and initializes the resources required * to build a scsi_pkt structure the target driver. The result * of the allocation and initialization will be cached in the * memory cache. As DMA resources may be allocated here, that * means DMA resources will be tied up in the cache manager. * This is a tradeoff that has been made for performance reasons. * * Argument: *buf Memory to preinitialize. * *arg FCP port structure (fcp_port). * kmflags Value passed to kmem_cache_alloc() and * propagated to the constructor. * * Return Value: 0 Allocation/Initialization was successful. * -1 Allocation or Initialization failed. * * * If the returned value is 0, the buffer is initialized like this: * * +================================+ * +----> | struct scsi_pkt | * | | | * | +--- | pkt_ha_private | * | | | | * | | +================================+ * | | * | | +================================+ * | +--> | struct fcp_pkt | <---------+ * | | | | * +----- | cmd_pkt | | * | cmd_fp_pkt | ---+ | * +-------->| cmd_fcp_rsp[] | | | * | +--->| cmd_fcp_cmd[] | | | * | | |--------------------------------| | | * | | | struct fc_packet | <--+ | * | | | | | * | | | pkt_ulp_private | ----------+ * | | | pkt_fca_private | -----+ * | | | pkt_data_cookie | ---+ | * | | | pkt_cmdlen | | | * | |(a) | pkt_rsplen | | | * | +----| .......... pkt_cmd ........... | ---|-|---------------+ * | (b) | pkt_cmd_cookie | ---|-|----------+ | * +---------| .......... pkt_resp .......... | ---|-|------+ | | * | pkt_resp_cookie | ---|-|--+ | | | * | pkt_cmd_dma | | | | | | | * | pkt_cmd_acc | | | | | | | * +================================+ | | | | | | * | dma_cookies | <--+ | | | | | * | | | | | | | * +================================+ | | | | | * | fca_private | <----+ | | | | * | | | | | | * +================================+ | | | | * | | | | * | | | | * +================================+ (d) | | | | * | fcp_resp cookies | <-------+ | | | * | | | | | * +================================+ | | | * | | | * +================================+ (d) | | | * | fcp_resp | <-----------+ | | * | (DMA resources associated) | | | * +================================+ | | * | | * | | * | | * +================================+ (c) | | * | fcp_cmd cookies | <---------------+ | * | | | * +================================+ | * | * +================================+ (c) | * | fcp_cmd | <--------------------+ * | (DMA resources associated) | * +================================+ * * (a) Only if DMA is NOT used for the FCP_CMD buffer. * (b) Only if DMA is NOT used for the FCP_RESP buffer * (c) Only if DMA is used for the FCP_CMD buffer. * (d) Only if DMA is used for the FCP_RESP buffer */ static int fcp_kmem_cache_constructor(struct scsi_pkt *pkt, scsi_hba_tran_t *tran, int kmflags) { struct fcp_pkt *cmd; struct fcp_port *pptr; fc_packet_t *fpkt; pptr = (struct fcp_port *)tran->tran_hba_private; cmd = (struct fcp_pkt *)pkt->pkt_ha_private; bzero(cmd, tran->tran_hba_len); cmd->cmd_pkt = pkt; pkt->pkt_cdbp = cmd->cmd_fcp_cmd.fcp_cdb; fpkt = (fc_packet_t *)&cmd->cmd_fc_packet; cmd->cmd_fp_pkt = fpkt; cmd->cmd_pkt->pkt_ha_private = (opaque_t)cmd; cmd->cmd_fp_pkt->pkt_ulp_private = (opaque_t)cmd; cmd->cmd_fp_pkt->pkt_fca_private = (opaque_t)((caddr_t)cmd + sizeof (struct fcp_pkt) + pptr->port_dmacookie_sz); fpkt->pkt_data_cookie = (ddi_dma_cookie_t *)((caddr_t)cmd + sizeof (struct fcp_pkt)); fpkt->pkt_cmdlen = sizeof (struct fcp_cmd); fpkt->pkt_rsplen = FCP_MAX_RSP_IU_SIZE; if (pptr->port_fcp_dma == FC_NO_DVMA_SPACE) { /* * The underlying HBA doesn't want to DMA the fcp_cmd or * fcp_resp. The transfer of information will be done by * bcopy. * The naming of the flags (that is actually a value) is * unfortunate. FC_NO_DVMA_SPACE doesn't mean "NO VIRTUAL * DMA" but instead "NO DMA". */ fpkt->pkt_resp_acc = fpkt->pkt_cmd_acc = NULL; fpkt->pkt_cmd = (caddr_t)&cmd->cmd_fcp_cmd; fpkt->pkt_resp = cmd->cmd_fcp_rsp; } else { /* * The underlying HBA will dma the fcp_cmd buffer and fcp_resp * buffer. A buffer is allocated for each one the ddi_dma_* * interfaces. */ if (fcp_alloc_cmd_resp(pptr, fpkt, kmflags) != FC_SUCCESS) { return (-1); } } return (0); } /* * Function: fcp_kmem_cache_destructor * * Description: Called by the destructor of the cache managed by SCSA. * All the resources pre-allocated in fcp_pkt_constructor * and the data also pre-initialized in fcp_pkt_constructor * are freed and uninitialized here. * * Argument: *buf Memory to uninitialize. * *arg FCP port structure (fcp_port). * * Return Value: None * * Context: kernel */ static void fcp_kmem_cache_destructor(struct scsi_pkt *pkt, scsi_hba_tran_t *tran) { struct fcp_pkt *cmd; struct fcp_port *pptr; pptr = (struct fcp_port *)(tran->tran_hba_private); cmd = pkt->pkt_ha_private; if (pptr->port_fcp_dma != FC_NO_DVMA_SPACE) { /* * If DMA was used to transfer the FCP_CMD and FCP_RESP, the * buffer and DMA resources allocated to do so are released. */ fcp_free_cmd_resp(pptr, cmd->cmd_fp_pkt); } } /* * Function: fcp_alloc_cmd_resp * * Description: This function allocated an FCP_CMD and FCP_RESP buffer that * will be DMAed by the HBA. The buffer is allocated applying * the DMA requirements for the HBA. The buffers allocated will * also be bound. DMA resources are allocated in the process. * They will be released by fcp_free_cmd_resp(). * * Argument: *pptr FCP port. * *fpkt fc packet for which the cmd and resp packet should be * allocated. * flags Allocation flags. * * Return Value: FC_FAILURE * FC_SUCCESS * * Context: User or Kernel context only if flags == KM_SLEEP. * Interrupt context if the KM_SLEEP is not specified. */ static int fcp_alloc_cmd_resp(struct fcp_port *pptr, fc_packet_t *fpkt, int flags) { int rval; int cmd_len; int resp_len; ulong_t real_len; int (*cb) (caddr_t); ddi_dma_cookie_t pkt_cookie; ddi_dma_cookie_t *cp; uint32_t cnt; cb = (flags == KM_SLEEP) ? DDI_DMA_SLEEP : DDI_DMA_DONTWAIT; cmd_len = fpkt->pkt_cmdlen; resp_len = fpkt->pkt_rsplen; ASSERT(fpkt->pkt_cmd_dma == NULL); /* Allocation of a DMA handle used in subsequent calls. */ if (ddi_dma_alloc_handle(pptr->port_dip, &pptr->port_cmd_dma_attr, cb, NULL, &fpkt->pkt_cmd_dma) != DDI_SUCCESS) { return (FC_FAILURE); } /* A buffer is allocated that satisfies the DMA requirements. */ rval = ddi_dma_mem_alloc(fpkt->pkt_cmd_dma, cmd_len, &pptr->port_dma_acc_attr, DDI_DMA_CONSISTENT, cb, NULL, (caddr_t *)&fpkt->pkt_cmd, &real_len, &fpkt->pkt_cmd_acc); if (rval != DDI_SUCCESS) { ddi_dma_free_handle(&fpkt->pkt_cmd_dma); return (FC_FAILURE); } if (real_len < cmd_len) { ddi_dma_mem_free(&fpkt->pkt_cmd_acc); ddi_dma_free_handle(&fpkt->pkt_cmd_dma); return (FC_FAILURE); } /* The buffer allocated is DMA bound. */ rval = ddi_dma_addr_bind_handle(fpkt->pkt_cmd_dma, NULL, fpkt->pkt_cmd, real_len, DDI_DMA_WRITE | DDI_DMA_CONSISTENT, cb, NULL, &pkt_cookie, &fpkt->pkt_cmd_cookie_cnt); if (rval != DDI_DMA_MAPPED) { ddi_dma_mem_free(&fpkt->pkt_cmd_acc); ddi_dma_free_handle(&fpkt->pkt_cmd_dma); return (FC_FAILURE); } if (fpkt->pkt_cmd_cookie_cnt > pptr->port_cmd_dma_attr.dma_attr_sgllen) { (void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma); ddi_dma_mem_free(&fpkt->pkt_cmd_acc); ddi_dma_free_handle(&fpkt->pkt_cmd_dma); return (FC_FAILURE); } ASSERT(fpkt->pkt_cmd_cookie_cnt != 0); /* * The buffer where the scatter/gather list is going to be built is * allocated. */ cp = fpkt->pkt_cmd_cookie = (ddi_dma_cookie_t *)kmem_alloc( fpkt->pkt_cmd_cookie_cnt * sizeof (pkt_cookie), KM_NOSLEEP); if (cp == NULL) { (void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma); ddi_dma_mem_free(&fpkt->pkt_cmd_acc); ddi_dma_free_handle(&fpkt->pkt_cmd_dma); return (FC_FAILURE); } /* * The scatter/gather list for the buffer we just allocated is built * here. */ *cp = pkt_cookie; cp++; for (cnt = 1; cnt < fpkt->pkt_cmd_cookie_cnt; cnt++, cp++) { ddi_dma_nextcookie(fpkt->pkt_cmd_dma, &pkt_cookie); *cp = pkt_cookie; } ASSERT(fpkt->pkt_resp_dma == NULL); if (ddi_dma_alloc_handle(pptr->port_dip, &pptr->port_resp_dma_attr, cb, NULL, &fpkt->pkt_resp_dma) != DDI_SUCCESS) { (void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma); ddi_dma_mem_free(&fpkt->pkt_cmd_acc); ddi_dma_free_handle(&fpkt->pkt_cmd_dma); return (FC_FAILURE); } rval = ddi_dma_mem_alloc(fpkt->pkt_resp_dma, resp_len, &pptr->port_dma_acc_attr, DDI_DMA_CONSISTENT, cb, NULL, (caddr_t *)&fpkt->pkt_resp, &real_len, &fpkt->pkt_resp_acc); if (rval != DDI_SUCCESS) { ddi_dma_free_handle(&fpkt->pkt_resp_dma); (void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma); ddi_dma_mem_free(&fpkt->pkt_cmd_acc); ddi_dma_free_handle(&fpkt->pkt_cmd_dma); kmem_free(fpkt->pkt_cmd_cookie, fpkt->pkt_cmd_cookie_cnt * sizeof (pkt_cookie)); return (FC_FAILURE); } if (real_len < resp_len) { ddi_dma_mem_free(&fpkt->pkt_resp_acc); ddi_dma_free_handle(&fpkt->pkt_resp_dma); (void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma); ddi_dma_mem_free(&fpkt->pkt_cmd_acc); ddi_dma_free_handle(&fpkt->pkt_cmd_dma); kmem_free(fpkt->pkt_cmd_cookie, fpkt->pkt_cmd_cookie_cnt * sizeof (pkt_cookie)); return (FC_FAILURE); } rval = ddi_dma_addr_bind_handle(fpkt->pkt_resp_dma, NULL, fpkt->pkt_resp, real_len, DDI_DMA_READ | DDI_DMA_CONSISTENT, cb, NULL, &pkt_cookie, &fpkt->pkt_resp_cookie_cnt); if (rval != DDI_DMA_MAPPED) { ddi_dma_mem_free(&fpkt->pkt_resp_acc); ddi_dma_free_handle(&fpkt->pkt_resp_dma); (void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma); ddi_dma_mem_free(&fpkt->pkt_cmd_acc); ddi_dma_free_handle(&fpkt->pkt_cmd_dma); kmem_free(fpkt->pkt_cmd_cookie, fpkt->pkt_cmd_cookie_cnt * sizeof (pkt_cookie)); return (FC_FAILURE); } if (fpkt->pkt_resp_cookie_cnt > pptr->port_resp_dma_attr.dma_attr_sgllen) { ddi_dma_mem_free(&fpkt->pkt_resp_acc); ddi_dma_free_handle(&fpkt->pkt_resp_dma); (void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma); ddi_dma_mem_free(&fpkt->pkt_cmd_acc); ddi_dma_free_handle(&fpkt->pkt_cmd_dma); kmem_free(fpkt->pkt_cmd_cookie, fpkt->pkt_cmd_cookie_cnt * sizeof (pkt_cookie)); return (FC_FAILURE); } ASSERT(fpkt->pkt_resp_cookie_cnt != 0); cp = fpkt->pkt_resp_cookie = (ddi_dma_cookie_t *)kmem_alloc( fpkt->pkt_resp_cookie_cnt * sizeof (pkt_cookie), KM_NOSLEEP); if (cp == NULL) { ddi_dma_mem_free(&fpkt->pkt_resp_acc); ddi_dma_free_handle(&fpkt->pkt_resp_dma); (void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma); ddi_dma_mem_free(&fpkt->pkt_cmd_acc); ddi_dma_free_handle(&fpkt->pkt_cmd_dma); kmem_free(fpkt->pkt_cmd_cookie, fpkt->pkt_cmd_cookie_cnt * sizeof (pkt_cookie)); return (FC_FAILURE); } *cp = pkt_cookie; cp++; for (cnt = 1; cnt < fpkt->pkt_resp_cookie_cnt; cnt++, cp++) { ddi_dma_nextcookie(fpkt->pkt_resp_dma, &pkt_cookie); *cp = pkt_cookie; } return (FC_SUCCESS); } /* * Function: fcp_free_cmd_resp * * Description: This function releases the FCP_CMD and FCP_RESP buffer * allocated by fcp_alloc_cmd_resp() and all the resources * associated with them. That includes the DMA resources and the * buffer allocated for the cookies of each one of them. * * Argument: *pptr FCP port context. * *fpkt fc packet containing the cmd and resp packet * to be released. * * Return Value: None * * Context: Interrupt, User and Kernel context. */ /* ARGSUSED */ static void fcp_free_cmd_resp(struct fcp_port *pptr, fc_packet_t *fpkt) { ASSERT(fpkt->pkt_resp_dma != NULL && fpkt->pkt_cmd_dma != NULL); if (fpkt->pkt_resp_dma) { (void) ddi_dma_unbind_handle(fpkt->pkt_resp_dma); ddi_dma_mem_free(&fpkt->pkt_resp_acc); ddi_dma_free_handle(&fpkt->pkt_resp_dma); } if (fpkt->pkt_resp_cookie) { kmem_free(fpkt->pkt_resp_cookie, fpkt->pkt_resp_cookie_cnt * sizeof (ddi_dma_cookie_t)); fpkt->pkt_resp_cookie = NULL; } if (fpkt->pkt_cmd_dma) { (void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma); ddi_dma_mem_free(&fpkt->pkt_cmd_acc); ddi_dma_free_handle(&fpkt->pkt_cmd_dma); } if (fpkt->pkt_cmd_cookie) { kmem_free(fpkt->pkt_cmd_cookie, fpkt->pkt_cmd_cookie_cnt * sizeof (ddi_dma_cookie_t)); fpkt->pkt_cmd_cookie = NULL; } } /* * called by the transport to do our own target initialization * * can acquire and release the global mutex */ /* ARGSUSED */ static int fcp_phys_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *hba_tran, struct scsi_device *sd) { uchar_t *bytes; uint_t nbytes; uint16_t lun_num; struct fcp_tgt *ptgt; struct fcp_lun *plun; struct fcp_port *pptr = (struct fcp_port *) hba_tran->tran_hba_private; ASSERT(pptr != NULL); FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_8, 0, "fcp_phys_tgt_init: called for %s (instance %d)", ddi_get_name(tgt_dip), ddi_get_instance(tgt_dip)); /* get our port WWN property */ bytes = NULL; if ((scsi_device_prop_lookup_byte_array(sd, SCSI_DEVICE_PROP_PATH, PORT_WWN_PROP, &bytes, &nbytes) != DDI_PROP_SUCCESS) || (nbytes != FC_WWN_SIZE)) { /* no port WWN property */ FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_8, 0, "fcp_phys_tgt_init: Returning DDI_NOT_WELL_FORMED" " for %s (instance %d): bytes=%p nbytes=%x", ddi_get_name(tgt_dip), ddi_get_instance(tgt_dip), bytes, nbytes); if (bytes != NULL) { scsi_device_prop_free(sd, SCSI_DEVICE_PROP_PATH, bytes); } return (DDI_NOT_WELL_FORMED); } ASSERT(bytes != NULL); lun_num = scsi_device_prop_get_int(sd, SCSI_DEVICE_PROP_PATH, LUN_PROP, 0xFFFF); if (lun_num == 0xFFFF) { FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_8, 0, "fcp_phys_tgt_init: Returning DDI_FAILURE:lun" " for %s (instance %d)", ddi_get_name(tgt_dip), ddi_get_instance(tgt_dip)); scsi_device_prop_free(sd, SCSI_DEVICE_PROP_PATH, bytes); return (DDI_NOT_WELL_FORMED); } mutex_enter(&pptr->port_mutex); if ((plun = fcp_lookup_lun(pptr, bytes, lun_num)) == NULL) { mutex_exit(&pptr->port_mutex); FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_8, 0, "fcp_phys_tgt_init: Returning DDI_FAILURE: No Lun" " for %s (instance %d)", ddi_get_name(tgt_dip), ddi_get_instance(tgt_dip)); scsi_device_prop_free(sd, SCSI_DEVICE_PROP_PATH, bytes); return (DDI_FAILURE); } ASSERT(bcmp(plun->lun_tgt->tgt_port_wwn.raw_wwn, bytes, FC_WWN_SIZE) == 0); ASSERT(plun->lun_num == lun_num); scsi_device_prop_free(sd, SCSI_DEVICE_PROP_PATH, bytes); ptgt = plun->lun_tgt; mutex_enter(&ptgt->tgt_mutex); plun->lun_tgt_count++; scsi_device_hba_private_set(sd, plun); plun->lun_state |= FCP_SCSI_LUN_TGT_INIT; plun->lun_tran = hba_tran; mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); return (DDI_SUCCESS); } /*ARGSUSED*/ static int fcp_virt_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *hba_tran, struct scsi_device *sd) { uchar_t *bytes; uint_t nbytes; uint16_t lun_num; struct fcp_tgt *ptgt; struct fcp_lun *plun; struct fcp_port *pptr = (struct fcp_port *) hba_tran->tran_hba_private; child_info_t *cip; ASSERT(pptr != NULL); FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_8, 0, "fcp_virt_tgt_init: called for %s (instance %d) (hba_dip %p)," " (tgt_dip %p)", ddi_get_name(tgt_dip), ddi_get_instance(tgt_dip), hba_dip, tgt_dip); cip = (child_info_t *)sd->sd_pathinfo; if (cip == NULL) { FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_8, 0, "fcp_virt_tgt_init: Returning DDI_NOT_WELL_FORMED" " for %s (instance %d)", ddi_get_name(tgt_dip), ddi_get_instance(tgt_dip)); return (DDI_NOT_WELL_FORMED); } /* get our port WWN property */ bytes = NULL; if ((scsi_device_prop_lookup_byte_array(sd, SCSI_DEVICE_PROP_PATH, PORT_WWN_PROP, &bytes, &nbytes) != DDI_PROP_SUCCESS) || (nbytes != FC_WWN_SIZE)) { if (bytes) scsi_device_prop_free(sd, SCSI_DEVICE_PROP_PATH, bytes); return (DDI_NOT_WELL_FORMED); } ASSERT(bytes != NULL); lun_num = scsi_device_prop_get_int(sd, SCSI_DEVICE_PROP_PATH, LUN_PROP, 0xFFFF); if (lun_num == 0xFFFF) { FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_8, 0, "fcp_virt_tgt_init: Returning DDI_FAILURE:lun" " for %s (instance %d)", ddi_get_name(tgt_dip), ddi_get_instance(tgt_dip)); scsi_device_prop_free(sd, SCSI_DEVICE_PROP_PATH, bytes); return (DDI_NOT_WELL_FORMED); } mutex_enter(&pptr->port_mutex); if ((plun = fcp_lookup_lun(pptr, bytes, lun_num)) == NULL) { mutex_exit(&pptr->port_mutex); FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_8, 0, "fcp_virt_tgt_init: Returning DDI_FAILURE: No Lun" " for %s (instance %d)", ddi_get_name(tgt_dip), ddi_get_instance(tgt_dip)); scsi_device_prop_free(sd, SCSI_DEVICE_PROP_PATH, bytes); return (DDI_FAILURE); } ASSERT(bcmp(plun->lun_tgt->tgt_port_wwn.raw_wwn, bytes, FC_WWN_SIZE) == 0); ASSERT(plun->lun_num == lun_num); scsi_device_prop_free(sd, SCSI_DEVICE_PROP_PATH, bytes); ptgt = plun->lun_tgt; mutex_enter(&ptgt->tgt_mutex); plun->lun_tgt_count++; scsi_device_hba_private_set(sd, plun); plun->lun_state |= FCP_SCSI_LUN_TGT_INIT; plun->lun_tran = hba_tran; mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); return (DDI_SUCCESS); } /* * called by the transport to do our own target initialization * * can acquire and release the global mutex */ /* ARGSUSED */ static int fcp_scsi_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *hba_tran, struct scsi_device *sd) { struct fcp_port *pptr = (struct fcp_port *) hba_tran->tran_hba_private; int rval; ASSERT(pptr != NULL); /* * Child node is getting initialized. Look at the mpxio component * type on the child device to see if this device is mpxio managed * or not. */ if (mdi_component_is_client(tgt_dip, NULL) == MDI_SUCCESS) { rval = fcp_virt_tgt_init(hba_dip, tgt_dip, hba_tran, sd); } else { rval = fcp_phys_tgt_init(hba_dip, tgt_dip, hba_tran, sd); } return (rval); } /* ARGSUSED */ static void fcp_scsi_tgt_free(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *hba_tran, struct scsi_device *sd) { struct fcp_lun *plun = scsi_device_hba_private_get(sd); struct fcp_tgt *ptgt; FCP_DTRACE(fcp_logq, LUN_PORT->port_instbuf, fcp_trace, FCP_BUF_LEVEL_8, 0, "fcp_scsi_tgt_free: called for tran %s%d, dev %s%d", ddi_get_name(hba_dip), ddi_get_instance(hba_dip), ddi_get_name(tgt_dip), ddi_get_instance(tgt_dip)); if (plun == NULL) { return; } ptgt = plun->lun_tgt; ASSERT(ptgt != NULL); mutex_enter(&ptgt->tgt_mutex); ASSERT(plun->lun_tgt_count > 0); if (--plun->lun_tgt_count == 0) { plun->lun_state &= ~FCP_SCSI_LUN_TGT_INIT; } plun->lun_tran = NULL; mutex_exit(&ptgt->tgt_mutex); } /* * Function: fcp_scsi_start * * Description: This function is called by the target driver to request a * command to be sent. * * Argument: *ap SCSI address of the device. * *pkt SCSI packet containing the cmd to send. * * Return Value: TRAN_ACCEPT * TRAN_BUSY * TRAN_BADPKT * TRAN_FATAL_ERROR */ static int fcp_scsi_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct fcp_port *pptr = ADDR2FCP(ap); struct fcp_lun *plun = ADDR2LUN(ap); struct fcp_pkt *cmd = PKT2CMD(pkt); struct fcp_tgt *ptgt = plun->lun_tgt; int rval; /* ensure command isn't already issued */ ASSERT(cmd->cmd_state != FCP_PKT_ISSUED); FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_9, 0, "fcp_transport Invoked for %x", plun->lun_tgt->tgt_d_id); /* * It is strange that we enter the fcp_port mutex and the target * mutex to check the lun state (which has a mutex of its own). */ mutex_enter(&pptr->port_mutex); mutex_enter(&ptgt->tgt_mutex); /* * If the device is offline and is not in the process of coming * online, fail the request. */ if ((plun->lun_state & FCP_LUN_OFFLINE) && !(plun->lun_state & FCP_LUN_ONLINING)) { mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); if (cmd->cmd_fp_pkt->pkt_pd == NULL) { pkt->pkt_reason = CMD_DEV_GONE; } return (TRAN_FATAL_ERROR); } cmd->cmd_fp_pkt->pkt_timeout = pkt->pkt_time; /* * If we are suspended, kernel is trying to dump, so don't * block, fail or defer requests - send them down right away. * NOTE: If we are in panic (i.e. trying to dump), we can't * assume we have been suspended. There is hardware such as * the v880 that doesn't do PM. Thus, the check for * ddi_in_panic. * * If FCP_STATE_IN_CB_DEVC is set, devices are in the process * of changing. So, if we can queue the packet, do it. Eventually, * either the device will have gone away or changed and we can fail * the request, or we can proceed if the device didn't change. * * If the pd in the target or the packet is NULL it's probably * because the device has gone away, we allow the request to be * put on the internal queue here in case the device comes back within * the offline timeout. fctl will fix up the pd's if the tgt_pd_handle * has gone NULL, while fcp deals cases where pkt_pd is NULL. pkt_pd * could be NULL because the device was disappearing during or since * packet initialization. */ if (((plun->lun_state & FCP_LUN_BUSY) && (!(pptr->port_state & FCP_STATE_SUSPENDED)) && !ddi_in_panic()) || (pptr->port_state & (FCP_STATE_ONLINING | FCP_STATE_IN_CB_DEVC)) || (ptgt->tgt_pd_handle == NULL) || (cmd->cmd_fp_pkt->pkt_pd == NULL)) { /* * If ((LUN is busy AND * LUN not suspended AND * The system is not in panic state) OR * (The port is coming up)) * * We check to see if the any of the flags FLAG_NOINTR or * FLAG_NOQUEUE is set. If one of them is set the value * returned will be TRAN_BUSY. If not, the request is queued. */ mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); /* see if using interrupts is allowed (so queueing'll work) */ if (pkt->pkt_flags & FLAG_NOINTR) { pkt->pkt_resid = 0; return (TRAN_BUSY); } if (pkt->pkt_flags & FLAG_NOQUEUE) { FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_9, 0, "fcp_scsi_start: lun busy for pkt %p", pkt); return (TRAN_BUSY); } #ifdef DEBUG mutex_enter(&pptr->port_pkt_mutex); pptr->port_npkts++; mutex_exit(&pptr->port_pkt_mutex); #endif /* DEBUG */ /* got queue up the pkt for later */ fcp_queue_pkt(pptr, cmd); return (TRAN_ACCEPT); } cmd->cmd_state = FCP_PKT_ISSUED; mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); /* * Now that we released the mutexes, what was protected by them can * change. */ /* * If there is a reconfiguration in progress, wait for it to complete. */ fcp_reconfig_wait(pptr); cmd->cmd_timeout = pkt->pkt_time ? fcp_watchdog_time + pkt->pkt_time : 0; /* prepare the packet */ fcp_prepare_pkt(pptr, cmd, plun); if (cmd->cmd_pkt->pkt_time) { cmd->cmd_fp_pkt->pkt_timeout = cmd->cmd_pkt->pkt_time; } else { cmd->cmd_fp_pkt->pkt_timeout = 5 * 60 * 60; } /* * if interrupts aren't allowed (e.g. at dump time) then we'll * have to do polled I/O */ if (pkt->pkt_flags & FLAG_NOINTR) { cmd->cmd_state &= ~FCP_PKT_ISSUED; return (fcp_dopoll(pptr, cmd)); } #ifdef DEBUG mutex_enter(&pptr->port_pkt_mutex); pptr->port_npkts++; mutex_exit(&pptr->port_pkt_mutex); #endif /* DEBUG */ rval = fcp_transport(pptr->port_fp_handle, cmd->cmd_fp_pkt, 0); if (rval == FC_SUCCESS) { FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_9, 0, "fcp_transport success for %x", plun->lun_tgt->tgt_d_id); return (TRAN_ACCEPT); } cmd->cmd_state = FCP_PKT_IDLE; #ifdef DEBUG mutex_enter(&pptr->port_pkt_mutex); pptr->port_npkts--; mutex_exit(&pptr->port_pkt_mutex); #endif /* DEBUG */ /* * For lack of clearer definitions, choose * between TRAN_BUSY and TRAN_FATAL_ERROR. */ if (rval == FC_TRAN_BUSY) { pkt->pkt_resid = 0; rval = TRAN_BUSY; } else { mutex_enter(&ptgt->tgt_mutex); if (plun->lun_state & FCP_LUN_OFFLINE) { child_info_t *cip; mutex_enter(&plun->lun_mutex); cip = plun->lun_cip; mutex_exit(&plun->lun_mutex); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_6, 0, "fcp_transport failed 2 for %x: %x; dip=%p", plun->lun_tgt->tgt_d_id, rval, cip); rval = TRAN_FATAL_ERROR; } else { if (pkt->pkt_flags & FLAG_NOQUEUE) { FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_9, 0, "fcp_scsi_start: FC_BUSY for pkt %p", pkt); rval = TRAN_BUSY; } else { rval = TRAN_ACCEPT; fcp_queue_pkt(pptr, cmd); } } mutex_exit(&ptgt->tgt_mutex); } return (rval); } /* * called by the transport to abort a packet */ /*ARGSUSED*/ static int fcp_scsi_abort(struct scsi_address *ap, struct scsi_pkt *pkt) { int tgt_cnt; struct fcp_port *pptr = ADDR2FCP(ap); struct fcp_lun *plun = ADDR2LUN(ap); struct fcp_tgt *ptgt = plun->lun_tgt; if (pkt == NULL) { if (ptgt) { mutex_enter(&ptgt->tgt_mutex); tgt_cnt = ptgt->tgt_change_cnt; mutex_exit(&ptgt->tgt_mutex); fcp_abort_all(pptr, ptgt, plun, tgt_cnt); return (TRUE); } } return (FALSE); } /* * Perform reset */ int fcp_scsi_reset(struct scsi_address *ap, int level) { int rval = 0; struct fcp_port *pptr = ADDR2FCP(ap); struct fcp_lun *plun = ADDR2LUN(ap); struct fcp_tgt *ptgt = plun->lun_tgt; if (level == RESET_ALL) { if (fcp_linkreset(pptr, ap, KM_NOSLEEP) == FC_SUCCESS) { rval = 1; } } else if (level == RESET_TARGET || level == RESET_LUN) { /* * If we are in the middle of discovery, return * SUCCESS as this target will be rediscovered * anyway */ mutex_enter(&ptgt->tgt_mutex); if (ptgt->tgt_state & (FCP_TGT_OFFLINE | FCP_TGT_BUSY)) { mutex_exit(&ptgt->tgt_mutex); return (1); } mutex_exit(&ptgt->tgt_mutex); if (fcp_reset_target(ap, level) == FC_SUCCESS) { rval = 1; } } return (rval); } /* * called by the framework to get a SCSI capability */ static int fcp_scsi_getcap(struct scsi_address *ap, char *cap, int whom) { return (fcp_commoncap(ap, cap, 0, whom, 0)); } /* * called by the framework to set a SCSI capability */ static int fcp_scsi_setcap(struct scsi_address *ap, char *cap, int value, int whom) { return (fcp_commoncap(ap, cap, value, whom, 1)); } /* * Function: fcp_pkt_setup * * Description: This function sets up the scsi_pkt structure passed by the * caller. This function assumes fcp_pkt_constructor has been * called previously for the packet passed by the caller. If * successful this call will have the following results: * * - The resources needed that will be constant through out * the whole transaction are allocated. * - The fields that will be constant through out the whole * transaction are initialized. * - The scsi packet will be linked to the LUN structure * addressed by the transaction. * * Argument: * *pkt Pointer to a scsi_pkt structure. * callback * arg * * Return Value: 0 Success * !0 Failure * * Context: Kernel context or interrupt context */ /* ARGSUSED */ static int fcp_pkt_setup(struct scsi_pkt *pkt, int (*callback)(caddr_t arg), caddr_t arg) { struct fcp_pkt *cmd; struct fcp_port *pptr; struct fcp_lun *plun; struct fcp_tgt *ptgt; int kf; fc_packet_t *fpkt; fc_frame_hdr_t *hp; pptr = ADDR2FCP(&pkt->pkt_address); plun = ADDR2LUN(&pkt->pkt_address); ptgt = plun->lun_tgt; cmd = (struct fcp_pkt *)pkt->pkt_ha_private; fpkt = cmd->cmd_fp_pkt; /* * this request is for dma allocation only */ /* * First step of fcp_scsi_init_pkt: pkt allocation * We determine if the caller is willing to wait for the * resources. */ kf = (callback == SLEEP_FUNC) ? KM_SLEEP: KM_NOSLEEP; /* * Selective zeroing of the pkt. */ cmd->cmd_back = NULL; cmd->cmd_next = NULL; /* * Zero out fcp command */ bzero(&cmd->cmd_fcp_cmd, sizeof (cmd->cmd_fcp_cmd)); cmd->cmd_state = FCP_PKT_IDLE; fpkt = cmd->cmd_fp_pkt; fpkt->pkt_data_acc = NULL; mutex_enter(&ptgt->tgt_mutex); fpkt->pkt_pd = ptgt->tgt_pd_handle; if (fc_ulp_init_packet(pptr->port_fp_handle, fpkt, kf) != FC_SUCCESS) { mutex_exit(&ptgt->tgt_mutex); return (-1); } mutex_exit(&ptgt->tgt_mutex); /* Fill in the Fabric Channel Header */ hp = &fpkt->pkt_cmd_fhdr; hp->r_ctl = R_CTL_COMMAND; hp->rsvd = 0; hp->type = FC_TYPE_SCSI_FCP; hp->f_ctl = F_CTL_SEQ_INITIATIVE | F_CTL_FIRST_SEQ; hp->seq_id = 0; hp->df_ctl = 0; hp->seq_cnt = 0; hp->ox_id = 0xffff; hp->rx_id = 0xffff; hp->ro = 0; /* * A doubly linked list (cmd_forw, cmd_back) is built * out of every allocated packet on a per-lun basis * * The packets are maintained in the list so as to satisfy * scsi_abort() requests. At present (which is unlikely to * change in the future) nobody performs a real scsi_abort * in the SCSI target drivers (as they don't keep the packets * after doing scsi_transport - so they don't know how to * abort a packet other than sending a NULL to abort all * outstanding packets) */ mutex_enter(&plun->lun_mutex); if ((cmd->cmd_forw = plun->lun_pkt_head) != NULL) { plun->lun_pkt_head->cmd_back = cmd; } else { plun->lun_pkt_tail = cmd; } plun->lun_pkt_head = cmd; mutex_exit(&plun->lun_mutex); return (0); } /* * Function: fcp_pkt_teardown * * Description: This function releases a scsi_pkt structure and all the * resources attached to it. * * Argument: *pkt Pointer to a scsi_pkt structure. * * Return Value: None * * Context: User, Kernel or Interrupt context. */ static void fcp_pkt_teardown(struct scsi_pkt *pkt) { struct fcp_port *pptr = ADDR2FCP(&pkt->pkt_address); struct fcp_lun *plun = ADDR2LUN(&pkt->pkt_address); struct fcp_pkt *cmd = (struct fcp_pkt *)pkt->pkt_ha_private; /* * Remove the packet from the per-lun list */ mutex_enter(&plun->lun_mutex); if (cmd->cmd_back) { ASSERT(cmd != plun->lun_pkt_head); cmd->cmd_back->cmd_forw = cmd->cmd_forw; } else { ASSERT(cmd == plun->lun_pkt_head); plun->lun_pkt_head = cmd->cmd_forw; } if (cmd->cmd_forw) { cmd->cmd_forw->cmd_back = cmd->cmd_back; } else { ASSERT(cmd == plun->lun_pkt_tail); plun->lun_pkt_tail = cmd->cmd_back; } mutex_exit(&plun->lun_mutex); (void) fc_ulp_uninit_packet(pptr->port_fp_handle, cmd->cmd_fp_pkt); } /* * Routine for reset notification setup, to register or cancel. * This function is called by SCSA */ /*ARGSUSED*/ static int fcp_scsi_reset_notify(struct scsi_address *ap, int flag, void (*callback)(caddr_t), caddr_t arg) { struct fcp_port *pptr = ADDR2FCP(ap); return (scsi_hba_reset_notify_setup(ap, flag, callback, arg, &pptr->port_mutex, &pptr->port_reset_notify_listf)); } static int fcp_scsi_bus_get_eventcookie(dev_info_t *dip, dev_info_t *rdip, char *name, ddi_eventcookie_t *event_cookiep) { struct fcp_port *pptr = fcp_dip2port(dip); if (pptr == NULL) { return (DDI_FAILURE); } return (ndi_event_retrieve_cookie(pptr->port_ndi_event_hdl, rdip, name, event_cookiep, NDI_EVENT_NOPASS)); } static int fcp_scsi_bus_add_eventcall(dev_info_t *dip, dev_info_t *rdip, ddi_eventcookie_t eventid, void (*callback)(), void *arg, ddi_callback_id_t *cb_id) { struct fcp_port *pptr = fcp_dip2port(dip); if (pptr == NULL) { return (DDI_FAILURE); } return (ndi_event_add_callback(pptr->port_ndi_event_hdl, rdip, eventid, callback, arg, NDI_SLEEP, cb_id)); } static int fcp_scsi_bus_remove_eventcall(dev_info_t *dip, ddi_callback_id_t cb_id) { struct fcp_port *pptr = fcp_dip2port(dip); if (pptr == NULL) { return (DDI_FAILURE); } return (ndi_event_remove_callback(pptr->port_ndi_event_hdl, cb_id)); } /* * called by the transport to post an event */ static int fcp_scsi_bus_post_event(dev_info_t *dip, dev_info_t *rdip, ddi_eventcookie_t eventid, void *impldata) { struct fcp_port *pptr = fcp_dip2port(dip); if (pptr == NULL) { return (DDI_FAILURE); } return (ndi_event_run_callbacks(pptr->port_ndi_event_hdl, rdip, eventid, impldata)); } /* * A target in in many cases in Fibre Channel has a one to one relation * with a port identifier (which is also known as D_ID and also as AL_PA * in private Loop) On Fibre Channel-to-SCSI bridge boxes a target reset * will most likely result in resetting all LUNs (which means a reset will * occur on all the SCSI devices connected at the other end of the bridge) * That is the latest favorite topic for discussion, for, one can debate as * hot as one likes and come up with arguably a best solution to one's * satisfaction * * To stay on track and not digress much, here are the problems stated * briefly: * * SCSA doesn't define RESET_LUN, It defines RESET_TARGET, but the * target drivers use RESET_TARGET even if their instance is on a * LUN. Doesn't that sound a bit broken ? * * FCP SCSI (the current spec) only defines RESET TARGET in the * control fields of an FCP_CMND structure. It should have been * fixed right there, giving flexibility to the initiators to * minimize havoc that could be caused by resetting a target. */ static int fcp_reset_target(struct scsi_address *ap, int level) { int rval = FC_FAILURE; char lun_id[25]; struct fcp_port *pptr = ADDR2FCP(ap); struct fcp_lun *plun = ADDR2LUN(ap); struct fcp_tgt *ptgt = plun->lun_tgt; struct scsi_pkt *pkt; struct fcp_pkt *cmd; struct fcp_rsp *rsp; uint32_t tgt_cnt; struct fcp_rsp_info *rsp_info; struct fcp_reset_elem *p; int bval; if ((p = kmem_alloc(sizeof (struct fcp_reset_elem), KM_NOSLEEP)) == NULL) { return (rval); } mutex_enter(&ptgt->tgt_mutex); if (level == RESET_TARGET) { if (ptgt->tgt_state & (FCP_TGT_OFFLINE | FCP_TGT_BUSY)) { mutex_exit(&ptgt->tgt_mutex); kmem_free(p, sizeof (struct fcp_reset_elem)); return (rval); } fcp_update_tgt_state(ptgt, FCP_SET, FCP_LUN_BUSY); (void) strcpy(lun_id, " "); } else { if (plun->lun_state & (FCP_LUN_OFFLINE | FCP_LUN_BUSY)) { mutex_exit(&ptgt->tgt_mutex); kmem_free(p, sizeof (struct fcp_reset_elem)); return (rval); } fcp_update_lun_state(plun, FCP_SET, FCP_LUN_BUSY); (void) sprintf(lun_id, ", LUN=%d", plun->lun_num); } tgt_cnt = ptgt->tgt_change_cnt; mutex_exit(&ptgt->tgt_mutex); if ((pkt = scsi_init_pkt(ap, NULL, NULL, 0, 0, 0, 0, NULL, 0)) == NULL) { kmem_free(p, sizeof (struct fcp_reset_elem)); mutex_enter(&ptgt->tgt_mutex); fcp_update_tgt_state(ptgt, FCP_RESET, FCP_LUN_BUSY); mutex_exit(&ptgt->tgt_mutex); return (rval); } pkt->pkt_time = FCP_POLL_TIMEOUT; /* fill in cmd part of packet */ cmd = PKT2CMD(pkt); if (level == RESET_TARGET) { cmd->cmd_fcp_cmd.fcp_cntl.cntl_reset_tgt = 1; } else { cmd->cmd_fcp_cmd.fcp_cntl.cntl_reset_lun = 1; } cmd->cmd_fp_pkt->pkt_comp = NULL; cmd->cmd_pkt->pkt_flags |= FLAG_NOINTR; /* prepare a packet for transport */ fcp_prepare_pkt(pptr, cmd, plun); if (cmd->cmd_pkt->pkt_time) { cmd->cmd_fp_pkt->pkt_timeout = cmd->cmd_pkt->pkt_time; } else { cmd->cmd_fp_pkt->pkt_timeout = 5 * 60 * 60; } (void) fc_ulp_busy_port(pptr->port_fp_handle); bval = fcp_dopoll(pptr, cmd); fc_ulp_idle_port(pptr->port_fp_handle); /* submit the packet */ if (bval == TRAN_ACCEPT) { int error = 3; rsp = (struct fcp_rsp *)cmd->cmd_fcp_rsp; rsp_info = (struct fcp_rsp_info *)(cmd->cmd_fcp_rsp + sizeof (struct fcp_rsp)); if (rsp->fcp_u.fcp_status.rsp_len_set) { if (fcp_validate_fcp_response(rsp, pptr) == FC_SUCCESS) { if (pptr->port_fcp_dma != FC_NO_DVMA_SPACE) { FCP_CP_IN(cmd->cmd_fp_pkt->pkt_resp + sizeof (struct fcp_rsp), rsp_info, cmd->cmd_fp_pkt->pkt_resp_acc, sizeof (struct fcp_rsp_info)); } if (rsp_info->rsp_code == FCP_NO_FAILURE) { rval = FC_SUCCESS; error = 0; } else { error = 1; } } else { error = 2; } } switch (error) { case 0: fcp_log(CE_WARN, pptr->port_dip, "!FCP: WWN 0x%08x%08x %s reset successfully", *((int *)&ptgt->tgt_port_wwn.raw_wwn[0]), *((int *)&ptgt->tgt_port_wwn.raw_wwn[4]), lun_id); break; case 1: fcp_log(CE_WARN, pptr->port_dip, "!FCP: Reset to WWN 0x%08x%08x %s failed," " response code=%x", *((int *)&ptgt->tgt_port_wwn.raw_wwn[0]), *((int *)&ptgt->tgt_port_wwn.raw_wwn[4]), lun_id, rsp_info->rsp_code); break; case 2: fcp_log(CE_WARN, pptr->port_dip, "!FCP: Reset to WWN 0x%08x%08x %s failed," " Bad FCP response values: rsvd1=%x," " rsvd2=%x, sts-rsvd1=%x, sts-rsvd2=%x," " rsplen=%x, senselen=%x", *((int *)&ptgt->tgt_port_wwn.raw_wwn[0]), *((int *)&ptgt->tgt_port_wwn.raw_wwn[4]), lun_id, rsp->reserved_0, rsp->reserved_1, rsp->fcp_u.fcp_status.reserved_0, rsp->fcp_u.fcp_status.reserved_1, rsp->fcp_response_len, rsp->fcp_sense_len); break; default: fcp_log(CE_WARN, pptr->port_dip, "!FCP: Reset to WWN 0x%08x%08x %s failed", *((int *)&ptgt->tgt_port_wwn.raw_wwn[0]), *((int *)&ptgt->tgt_port_wwn.raw_wwn[4]), lun_id); break; } } scsi_destroy_pkt(pkt); if (rval == FC_FAILURE) { mutex_enter(&ptgt->tgt_mutex); if (level == RESET_TARGET) { fcp_update_tgt_state(ptgt, FCP_RESET, FCP_LUN_BUSY); } else { fcp_update_lun_state(plun, FCP_RESET, FCP_LUN_BUSY); } mutex_exit(&ptgt->tgt_mutex); kmem_free(p, sizeof (struct fcp_reset_elem)); return (rval); } mutex_enter(&pptr->port_mutex); if (level == RESET_TARGET) { p->tgt = ptgt; p->lun = NULL; } else { p->tgt = NULL; p->lun = plun; } p->tgt = ptgt; p->tgt_cnt = tgt_cnt; p->timeout = fcp_watchdog_time + FCP_RESET_DELAY; p->next = pptr->port_reset_list; pptr->port_reset_list = p; FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "Notify ssd of the reset to reinstate the reservations"); scsi_hba_reset_notify_callback(&pptr->port_mutex, &pptr->port_reset_notify_listf); mutex_exit(&pptr->port_mutex); return (rval); } /* * called by fcp_getcap and fcp_setcap to get and set (respectively) * SCSI capabilities */ /* ARGSUSED */ static int fcp_commoncap(struct scsi_address *ap, char *cap, int val, int tgtonly, int doset) { struct fcp_port *pptr = ADDR2FCP(ap); struct fcp_lun *plun = ADDR2LUN(ap); struct fcp_tgt *ptgt = plun->lun_tgt; int cidx; int rval = FALSE; if (cap == (char *)0) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_commoncap: invalid arg"); return (rval); } if ((cidx = scsi_hba_lookup_capstr(cap)) == -1) { return (UNDEFINED); } /* * Process setcap request. */ if (doset) { /* * At present, we can only set binary (0/1) values */ switch (cidx) { case SCSI_CAP_ARQ: if (val == 0) { rval = FALSE; } else { rval = TRUE; } break; case SCSI_CAP_LUN_RESET: if (val) { plun->lun_cap |= FCP_LUN_CAP_RESET; } else { plun->lun_cap &= ~FCP_LUN_CAP_RESET; } rval = TRUE; break; case SCSI_CAP_SECTOR_SIZE: rval = TRUE; break; default: FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_4, 0, "fcp_setcap: unsupported %d", cidx); rval = UNDEFINED; break; } FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_5, 0, "set cap: cap=%s, val/tgtonly/doset/rval = " "0x%x/0x%x/0x%x/%d", cap, val, tgtonly, doset, rval); } else { /* * Process getcap request. */ switch (cidx) { case SCSI_CAP_DMA_MAX: rval = (int)pptr->port_data_dma_attr.dma_attr_maxxfer; /* * Need to make an adjustment qlc is uint_t 64 * st is int, so we will make the adjustment here * being as nobody wants to touch this. * It still leaves the max single block length * of 2 gig. This should last . */ if (rval == -1) { rval = MAX_INT_DMA; } break; case SCSI_CAP_INITIATOR_ID: rval = pptr->port_id; break; case SCSI_CAP_ARQ: case SCSI_CAP_RESET_NOTIFICATION: case SCSI_CAP_TAGGED_QING: rval = TRUE; break; case SCSI_CAP_SCSI_VERSION: rval = 3; break; case SCSI_CAP_INTERCONNECT_TYPE: if (FC_TOP_EXTERNAL(pptr->port_topology) || (ptgt->tgt_hard_addr == 0)) { rval = INTERCONNECT_FABRIC; } else { rval = INTERCONNECT_FIBRE; } break; case SCSI_CAP_LUN_RESET: rval = ((plun->lun_cap & FCP_LUN_CAP_RESET) != 0) ? TRUE : FALSE; break; default: FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_4, 0, "fcp_getcap: unsupported %d", cidx); rval = UNDEFINED; break; } FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_8, 0, "get cap: cap=%s, val/tgtonly/doset/rval = " "0x%x/0x%x/0x%x/%d", cap, val, tgtonly, doset, rval); } return (rval); } /* * called by the transport to get the port-wwn and lun * properties of this device, and to create a "name" based on them * * these properties don't exist on sun4m * * return 1 for success else return 0 */ /* ARGSUSED */ static int fcp_scsi_get_name(struct scsi_device *sd, char *name, int len) { int i; int *lun; int numChars; uint_t nlun; uint_t count; uint_t nbytes; uchar_t *bytes; uint16_t lun_num; uint32_t tgt_id; char **conf_wwn; char tbuf[(FC_WWN_SIZE << 1) + 1]; uchar_t barray[FC_WWN_SIZE]; dev_info_t *tgt_dip; struct fcp_tgt *ptgt; struct fcp_port *pptr; struct fcp_lun *plun; ASSERT(sd != NULL); ASSERT(name != NULL); tgt_dip = sd->sd_dev; pptr = ddi_get_soft_state(fcp_softstate, ddi_get_instance(ddi_get_parent(tgt_dip))); if (pptr == NULL) { return (0); } ASSERT(tgt_dip != NULL); if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, sd->sd_dev, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, LUN_PROP, &lun, &nlun) != DDI_SUCCESS) { name[0] = '\0'; return (0); } if (nlun == 0) { ddi_prop_free(lun); return (0); } lun_num = lun[0]; ddi_prop_free(lun); /* * Lookup for .conf WWN property */ if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, tgt_dip, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, CONF_WWN_PROP, &conf_wwn, &count) == DDI_PROP_SUCCESS) { ASSERT(count >= 1); fcp_ascii_to_wwn(conf_wwn[0], barray, FC_WWN_SIZE); ddi_prop_free(conf_wwn); mutex_enter(&pptr->port_mutex); if ((plun = fcp_lookup_lun(pptr, barray, lun_num)) == NULL) { mutex_exit(&pptr->port_mutex); return (0); } ptgt = plun->lun_tgt; mutex_exit(&pptr->port_mutex); (void) ndi_prop_update_byte_array(DDI_DEV_T_NONE, tgt_dip, PORT_WWN_PROP, barray, FC_WWN_SIZE); if (!FC_TOP_EXTERNAL(pptr->port_topology) && ptgt->tgt_hard_addr != 0) { tgt_id = (uint32_t)fcp_alpa_to_switch[ ptgt->tgt_hard_addr]; } else { tgt_id = ptgt->tgt_d_id; } (void) ndi_prop_update_int(DDI_DEV_T_NONE, tgt_dip, TARGET_PROP, tgt_id); } /* get the our port-wwn property */ bytes = NULL; if ((ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, tgt_dip, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, PORT_WWN_PROP, &bytes, &nbytes) != DDI_PROP_SUCCESS) || nbytes != FC_WWN_SIZE) { if (bytes != NULL) { ddi_prop_free(bytes); } return (0); } for (i = 0; i < FC_WWN_SIZE; i++) { (void) sprintf(&tbuf[i << 1], "%02x", *(bytes + i)); } /* Stick in the address of the form "wWWN,LUN" */ numChars = snprintf(name, len, "w%s,%x", tbuf, lun_num); ASSERT(numChars < len); if (numChars >= len) { fcp_log(CE_WARN, pptr->port_dip, "!fcp_scsi_get_name: " "name parameter length too small, it needs to be %d", numChars+1); } ddi_prop_free(bytes); return (1); } /* * called by the transport to get the SCSI target id value, returning * it in "name" * * this isn't needed/used on sun4m * * return 1 for success else return 0 */ /* ARGSUSED */ static int fcp_scsi_get_bus_addr(struct scsi_device *sd, char *name, int len) { struct fcp_lun *plun = ADDR2LUN(&sd->sd_address); struct fcp_tgt *ptgt; int numChars; if (plun == NULL) { return (0); } if ((ptgt = plun->lun_tgt) == NULL) { return (0); } numChars = snprintf(name, len, "%x", ptgt->tgt_d_id); ASSERT(numChars < len); if (numChars >= len) { fcp_log(CE_WARN, NULL, "!fcp_scsi_get_bus_addr: " "name parameter length too small, it needs to be %d", numChars+1); } return (1); } /* * called internally to reset the link where the specified port lives */ static int fcp_linkreset(struct fcp_port *pptr, struct scsi_address *ap, int sleep) { la_wwn_t wwn; struct fcp_lun *plun; struct fcp_tgt *ptgt; /* disable restart of lip if we're suspended */ mutex_enter(&pptr->port_mutex); if (pptr->port_state & (FCP_STATE_SUSPENDED | FCP_STATE_POWER_DOWN)) { mutex_exit(&pptr->port_mutex); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_linkreset, fcp%d: link reset " "disabled due to DDI_SUSPEND", ddi_get_instance(pptr->port_dip)); return (FC_FAILURE); } if (pptr->port_state & (FCP_STATE_OFFLINE | FCP_STATE_ONLINING)) { mutex_exit(&pptr->port_mutex); return (FC_SUCCESS); } FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_8, 0, "Forcing link reset"); /* * If ap == NULL assume local link reset. */ if (FC_TOP_EXTERNAL(pptr->port_topology) && (ap != NULL)) { plun = ADDR2LUN(ap); ptgt = plun->lun_tgt; bcopy(&ptgt->tgt_port_wwn.raw_wwn[0], &wwn, sizeof (wwn)); } else { bzero((caddr_t)&wwn, sizeof (wwn)); } mutex_exit(&pptr->port_mutex); return (fc_ulp_linkreset(pptr->port_fp_handle, &wwn, sleep)); } /* * called from fcp_port_attach() to resume a port * return DDI_* success/failure status * acquires and releases the global mutex * acquires and releases the port mutex */ /*ARGSUSED*/ static int fcp_handle_port_resume(opaque_t ulph, fc_ulp_port_info_t *pinfo, uint32_t s_id, fc_attach_cmd_t cmd, int instance) { int res = DDI_FAILURE; /* default result */ struct fcp_port *pptr; /* port state ptr */ uint32_t alloc_cnt; uint32_t max_cnt; fc_portmap_t *tmp_list = NULL; FCP_DTRACE(fcp_logq, "fcp", fcp_trace, FCP_BUF_LEVEL_8, 0, "port resume: for port %d", instance); if ((pptr = ddi_get_soft_state(fcp_softstate, instance)) == NULL) { cmn_err(CE_WARN, "fcp: bad soft state"); return (res); } mutex_enter(&pptr->port_mutex); switch (cmd) { case FC_CMD_RESUME: ASSERT((pptr->port_state & FCP_STATE_POWER_DOWN) == 0); pptr->port_state &= ~FCP_STATE_SUSPENDED; break; case FC_CMD_POWER_UP: /* * If the port is DDI_SUSPENded, defer rediscovery * until DDI_RESUME occurs */ if (pptr->port_state & FCP_STATE_SUSPENDED) { pptr->port_state &= ~FCP_STATE_POWER_DOWN; mutex_exit(&pptr->port_mutex); return (DDI_SUCCESS); } pptr->port_state &= ~FCP_STATE_POWER_DOWN; } pptr->port_id = s_id; pptr->port_state = FCP_STATE_INIT; mutex_exit(&pptr->port_mutex); /* * Make a copy of ulp_port_info as fctl allocates * a temp struct. */ (void) fcp_cp_pinfo(pptr, pinfo); mutex_enter(&fcp_global_mutex); if (fcp_watchdog_init++ == 0) { fcp_watchdog_tick = fcp_watchdog_timeout * drv_usectohz(1000000); fcp_watchdog_id = timeout(fcp_watch, NULL, fcp_watchdog_tick); } mutex_exit(&fcp_global_mutex); /* * Handle various topologies and link states. */ switch (FC_PORT_STATE_MASK(pptr->port_phys_state)) { case FC_STATE_OFFLINE: /* * Wait for ONLINE, at which time a state * change will cause a statec_callback */ res = DDI_SUCCESS; break; case FC_STATE_ONLINE: if (pptr->port_topology == FC_TOP_UNKNOWN) { (void) fcp_linkreset(pptr, NULL, KM_NOSLEEP); res = DDI_SUCCESS; break; } if (FC_TOP_EXTERNAL(pptr->port_topology) && !fcp_enable_auto_configuration) { tmp_list = fcp_construct_map(pptr, &alloc_cnt); if (tmp_list == NULL) { if (!alloc_cnt) { res = DDI_SUCCESS; } break; } max_cnt = alloc_cnt; } else { ASSERT(pptr->port_topology != FC_TOP_UNKNOWN); alloc_cnt = FCP_MAX_DEVICES; if ((tmp_list = (fc_portmap_t *)kmem_zalloc( (sizeof (fc_portmap_t)) * alloc_cnt, KM_NOSLEEP)) == NULL) { fcp_log(CE_WARN, pptr->port_dip, "!fcp%d: failed to allocate portmap", instance); break; } max_cnt = alloc_cnt; if ((res = fc_ulp_getportmap(pptr->port_fp_handle, &tmp_list, &max_cnt, FC_ULP_PLOGI_PRESERVE)) != FC_SUCCESS) { caddr_t msg; (void) fc_ulp_error(res, &msg); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "resume failed getportmap: reason=0x%x", res); fcp_log(CE_WARN, pptr->port_dip, "!failed to get port map : %s", msg); break; } if (max_cnt > alloc_cnt) { alloc_cnt = max_cnt; } } /* * do the SCSI device discovery and create * the devinfos */ fcp_statec_callback(ulph, pptr->port_fp_handle, pptr->port_phys_state, pptr->port_topology, tmp_list, max_cnt, pptr->port_id); res = DDI_SUCCESS; break; default: fcp_log(CE_WARN, pptr->port_dip, "!fcp%d: invalid port state at attach=0x%x", instance, pptr->port_phys_state); mutex_enter(&pptr->port_mutex); pptr->port_phys_state = FCP_STATE_OFFLINE; mutex_exit(&pptr->port_mutex); res = DDI_SUCCESS; break; } if (tmp_list != NULL) { kmem_free(tmp_list, sizeof (fc_portmap_t) * alloc_cnt); } return (res); } static void fcp_cp_pinfo(struct fcp_port *pptr, fc_ulp_port_info_t *pinfo) { pptr->port_fp_modlinkage = *pinfo->port_linkage; pptr->port_dip = pinfo->port_dip; pptr->port_fp_handle = pinfo->port_handle; pptr->port_data_dma_attr = *pinfo->port_data_dma_attr; pptr->port_cmd_dma_attr = *pinfo->port_cmd_dma_attr; pptr->port_resp_dma_attr = *pinfo->port_resp_dma_attr; pptr->port_dma_acc_attr = *pinfo->port_acc_attr; pptr->port_priv_pkt_len = pinfo->port_fca_pkt_size; pptr->port_max_exch = pinfo->port_fca_max_exch; pptr->port_phys_state = pinfo->port_state; pptr->port_topology = pinfo->port_flags; pptr->port_reset_action = pinfo->port_reset_action; pptr->port_cmds_dma_flags = pinfo->port_dma_behavior; pptr->port_fcp_dma = pinfo->port_fcp_dma; bcopy(&pinfo->port_nwwn, &pptr->port_nwwn, sizeof (la_wwn_t)); bcopy(&pinfo->port_pwwn, &pptr->port_pwwn, sizeof (la_wwn_t)); } /* * If the elements wait field is set to 1 then * another thread is waiting for the operation to complete. Once * it is complete, the waiting thread is signaled and the element is * freed by the waiting thread. If the elements wait field is set to 0 * the element is freed. */ static void fcp_process_elem(struct fcp_hp_elem *elem, int result) { ASSERT(elem != NULL); mutex_enter(&elem->mutex); elem->result = result; if (elem->wait) { elem->wait = 0; cv_signal(&elem->cv); mutex_exit(&elem->mutex); } else { mutex_exit(&elem->mutex); cv_destroy(&elem->cv); mutex_destroy(&elem->mutex); kmem_free(elem, sizeof (struct fcp_hp_elem)); } } /* * This function is invoked from the taskq thread to allocate * devinfo nodes and to online/offline them. */ static void fcp_hp_task(void *arg) { struct fcp_hp_elem *elem = (struct fcp_hp_elem *)arg; struct fcp_lun *plun = elem->lun; struct fcp_port *pptr = elem->port; int result; ASSERT(elem->what == FCP_ONLINE || elem->what == FCP_OFFLINE || elem->what == FCP_MPXIO_PATH_CLEAR_BUSY || elem->what == FCP_MPXIO_PATH_SET_BUSY); mutex_enter(&pptr->port_mutex); mutex_enter(&plun->lun_mutex); if (((elem->what == FCP_ONLINE || elem->what == FCP_OFFLINE) && plun->lun_event_count != elem->event_cnt) || pptr->port_state & (FCP_STATE_SUSPENDED | FCP_STATE_DETACHING | FCP_STATE_POWER_DOWN)) { mutex_exit(&plun->lun_mutex); mutex_exit(&pptr->port_mutex); fcp_process_elem(elem, NDI_FAILURE); return; } mutex_exit(&plun->lun_mutex); mutex_exit(&pptr->port_mutex); result = fcp_trigger_lun(plun, elem->cip, elem->old_lun_mpxio, elem->what, elem->link_cnt, elem->tgt_cnt, elem->flags); fcp_process_elem(elem, result); } static child_info_t * fcp_get_cip(struct fcp_lun *plun, child_info_t *cip, int lcount, int tcount) { ASSERT(MUTEX_HELD(&plun->lun_mutex)); if (fcp_is_child_present(plun, cip) == FC_FAILURE) { struct fcp_port *pptr = plun->lun_tgt->tgt_port; ASSERT(MUTEX_HELD(&pptr->port_mutex)); /* * Child has not been created yet. Create the child device * based on the per-Lun flags. */ if (pptr->port_mpxio == 0 || plun->lun_mpxio == 0) { plun->lun_cip = CIP(fcp_create_dip(plun, lcount, tcount)); plun->lun_mpxio = 0; } else { plun->lun_cip = CIP(fcp_create_pip(plun, lcount, tcount)); plun->lun_mpxio = 1; } } else { plun->lun_cip = cip; } return (plun->lun_cip); } static int fcp_is_dip_present(struct fcp_lun *plun, dev_info_t *cdip) { int rval = FC_FAILURE; dev_info_t *pdip; struct dev_info *dip; int circular; ASSERT(MUTEX_HELD(&plun->lun_mutex)); pdip = plun->lun_tgt->tgt_port->port_dip; if (plun->lun_cip == NULL) { FCP_TRACE(fcp_logq, LUN_PORT->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_is_dip_present: plun->lun_cip is NULL: " "plun: %p lun state: %x num: %d target state: %x", plun, plun->lun_state, plun->lun_num, plun->lun_tgt->tgt_port->port_state); return (rval); } ndi_devi_enter(pdip, &circular); dip = DEVI(pdip)->devi_child; while (dip) { if (dip == DEVI(cdip)) { rval = FC_SUCCESS; break; } dip = dip->devi_sibling; } ndi_devi_exit(pdip, circular); return (rval); } static int fcp_is_child_present(struct fcp_lun *plun, child_info_t *cip) { int rval = FC_FAILURE; ASSERT(plun != NULL); ASSERT(MUTEX_HELD(&plun->lun_mutex)); if (plun->lun_mpxio == 0) { rval = fcp_is_dip_present(plun, DIP(cip)); } else { rval = fcp_is_pip_present(plun, PIP(cip)); } return (rval); } /* * Function: fcp_create_dip * * Description: Creates a dev_info_t structure for the LUN specified by the * caller. * * Argument: plun Lun structure * link_cnt Link state count. * tgt_cnt Target state change count. * * Return Value: NULL if it failed * dev_info_t structure address if it succeeded * * Context: Kernel context */ static dev_info_t * fcp_create_dip(struct fcp_lun *plun, int link_cnt, int tgt_cnt) { int failure = 0; uint32_t tgt_id; uint64_t sam_lun; struct fcp_tgt *ptgt = plun->lun_tgt; struct fcp_port *pptr = ptgt->tgt_port; dev_info_t *pdip = pptr->port_dip; dev_info_t *cdip = NULL; dev_info_t *old_dip = DIP(plun->lun_cip); char *nname = NULL; char **compatible = NULL; int ncompatible; char *scsi_binding_set; char t_pwwn[17]; ASSERT(MUTEX_HELD(&plun->lun_mutex)); ASSERT(MUTEX_HELD(&pptr->port_mutex)); /* get the 'scsi-binding-set' property */ if (ddi_prop_lookup_string(DDI_DEV_T_ANY, pdip, DDI_PROP_NOTPROM | DDI_PROP_DONTPASS, "scsi-binding-set", &scsi_binding_set) != DDI_PROP_SUCCESS) { scsi_binding_set = NULL; } /* determine the node name and compatible */ scsi_hba_nodename_compatible_get(&plun->lun_inq, scsi_binding_set, plun->lun_inq.inq_dtype, NULL, &nname, &compatible, &ncompatible); if (scsi_binding_set) { ddi_prop_free(scsi_binding_set); } if (nname == NULL) { #ifdef DEBUG cmn_err(CE_WARN, "%s%d: no driver for " "device @w%02x%02x%02x%02x%02x%02x%02x%02x,%d:" " compatible: %s", ddi_driver_name(pdip), ddi_get_instance(pdip), ptgt->tgt_port_wwn.raw_wwn[0], ptgt->tgt_port_wwn.raw_wwn[1], ptgt->tgt_port_wwn.raw_wwn[2], ptgt->tgt_port_wwn.raw_wwn[3], ptgt->tgt_port_wwn.raw_wwn[4], ptgt->tgt_port_wwn.raw_wwn[5], ptgt->tgt_port_wwn.raw_wwn[6], ptgt->tgt_port_wwn.raw_wwn[7], plun->lun_num, *compatible); #endif /* DEBUG */ failure++; goto end_of_fcp_create_dip; } cdip = fcp_find_existing_dip(plun, pdip, nname); /* * if the old_dip does not match the cdip, that means there is * some property change. since we'll be using the cdip, we need * to offline the old_dip. If the state contains FCP_LUN_CHANGED * then the dtype for the device has been updated. Offline the * the old device and create a new device with the new device type * Refer to bug: 4764752 */ if (old_dip && (cdip != old_dip || plun->lun_state & FCP_LUN_CHANGED)) { plun->lun_state &= ~(FCP_LUN_INIT); mutex_exit(&plun->lun_mutex); mutex_exit(&pptr->port_mutex); mutex_enter(&ptgt->tgt_mutex); (void) fcp_pass_to_hp(pptr, plun, CIP(old_dip), FCP_OFFLINE, link_cnt, tgt_cnt, NDI_DEVI_REMOVE, 0); mutex_exit(&ptgt->tgt_mutex); #ifdef DEBUG if (cdip != NULL) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "Old dip=%p; New dip=%p don't match", old_dip, cdip); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "Old dip=%p; New dip=NULL don't match", old_dip); } #endif mutex_enter(&pptr->port_mutex); mutex_enter(&plun->lun_mutex); } if (cdip == NULL || plun->lun_state & FCP_LUN_CHANGED) { plun->lun_state &= ~(FCP_LUN_CHANGED); if (ndi_devi_alloc(pptr->port_dip, nname, DEVI_SID_NODEID, &cdip) != NDI_SUCCESS) { failure++; goto end_of_fcp_create_dip; } } /* * Previously all the properties for the devinfo were destroyed here * with a call to ndi_prop_remove_all(). Since this may cause loss of * the devid property (and other properties established by the target * driver or framework) which the code does not always recreate, this * call was removed. * This opens a theoretical possibility that we may return with a * stale devid on the node if the scsi entity behind the fibre channel * lun has changed. */ /* decorate the node with compatible */ if (ndi_prop_update_string_array(DDI_DEV_T_NONE, cdip, "compatible", compatible, ncompatible) != DDI_PROP_SUCCESS) { failure++; goto end_of_fcp_create_dip; } if (ndi_prop_update_byte_array(DDI_DEV_T_NONE, cdip, NODE_WWN_PROP, ptgt->tgt_node_wwn.raw_wwn, FC_WWN_SIZE) != DDI_PROP_SUCCESS) { failure++; goto end_of_fcp_create_dip; } if (ndi_prop_update_byte_array(DDI_DEV_T_NONE, cdip, PORT_WWN_PROP, ptgt->tgt_port_wwn.raw_wwn, FC_WWN_SIZE) != DDI_PROP_SUCCESS) { failure++; goto end_of_fcp_create_dip; } fcp_wwn_to_ascii(ptgt->tgt_port_wwn.raw_wwn, t_pwwn); t_pwwn[16] = '\0'; if (ndi_prop_update_string(DDI_DEV_T_NONE, cdip, TGT_PORT_PROP, t_pwwn) != DDI_PROP_SUCCESS) { failure++; goto end_of_fcp_create_dip; } /* * If there is no hard address - We might have to deal with * that by using WWN - Having said that it is important to * recognize this problem early so ssd can be informed of * the right interconnect type. */ if (!FC_TOP_EXTERNAL(pptr->port_topology) && ptgt->tgt_hard_addr != 0) { tgt_id = (uint32_t)fcp_alpa_to_switch[ptgt->tgt_hard_addr]; } else { tgt_id = ptgt->tgt_d_id; } if (ndi_prop_update_int(DDI_DEV_T_NONE, cdip, TARGET_PROP, tgt_id) != DDI_PROP_SUCCESS) { failure++; goto end_of_fcp_create_dip; } if (ndi_prop_update_int(DDI_DEV_T_NONE, cdip, LUN_PROP, (int)plun->lun_num) != DDI_PROP_SUCCESS) { failure++; goto end_of_fcp_create_dip; } bcopy(&plun->lun_addr, &sam_lun, FCP_LUN_SIZE); if (ndi_prop_update_int64(DDI_DEV_T_NONE, cdip, SAM_LUN_PROP, sam_lun) != DDI_PROP_SUCCESS) { failure++; goto end_of_fcp_create_dip; } end_of_fcp_create_dip: scsi_hba_nodename_compatible_free(nname, compatible); if (cdip != NULL && failure) { (void) ndi_prop_remove_all(cdip); (void) ndi_devi_free(cdip); cdip = NULL; } return (cdip); } /* * Function: fcp_create_pip * * Description: Creates a Path Id for the LUN specified by the caller. * * Argument: plun Lun structure * link_cnt Link state count. * tgt_cnt Target state count. * * Return Value: NULL if it failed * mdi_pathinfo_t structure address if it succeeded * * Context: Kernel context */ static mdi_pathinfo_t * fcp_create_pip(struct fcp_lun *plun, int lcount, int tcount) { int i; char buf[MAXNAMELEN]; char uaddr[MAXNAMELEN]; int failure = 0; uint32_t tgt_id; uint64_t sam_lun; struct fcp_tgt *ptgt = plun->lun_tgt; struct fcp_port *pptr = ptgt->tgt_port; dev_info_t *pdip = pptr->port_dip; mdi_pathinfo_t *pip = NULL; mdi_pathinfo_t *old_pip = PIP(plun->lun_cip); char *nname = NULL; char **compatible = NULL; int ncompatible; char *scsi_binding_set; char t_pwwn[17]; ASSERT(MUTEX_HELD(&plun->lun_mutex)); ASSERT(MUTEX_HELD(&pptr->port_mutex)); scsi_binding_set = "vhci"; /* determine the node name and compatible */ scsi_hba_nodename_compatible_get(&plun->lun_inq, scsi_binding_set, plun->lun_inq.inq_dtype, NULL, &nname, &compatible, &ncompatible); if (nname == NULL) { #ifdef DEBUG cmn_err(CE_WARN, "fcp_create_dip: %s%d: no driver for " "device @w%02x%02x%02x%02x%02x%02x%02x%02x,%d:" " compatible: %s", ddi_driver_name(pdip), ddi_get_instance(pdip), ptgt->tgt_port_wwn.raw_wwn[0], ptgt->tgt_port_wwn.raw_wwn[1], ptgt->tgt_port_wwn.raw_wwn[2], ptgt->tgt_port_wwn.raw_wwn[3], ptgt->tgt_port_wwn.raw_wwn[4], ptgt->tgt_port_wwn.raw_wwn[5], ptgt->tgt_port_wwn.raw_wwn[6], ptgt->tgt_port_wwn.raw_wwn[7], plun->lun_num, *compatible); #endif /* DEBUG */ failure++; goto end_of_fcp_create_pip; } pip = fcp_find_existing_pip(plun, pdip); /* * if the old_dip does not match the cdip, that means there is * some property change. since we'll be using the cdip, we need * to offline the old_dip. If the state contains FCP_LUN_CHANGED * then the dtype for the device has been updated. Offline the * the old device and create a new device with the new device type * Refer to bug: 4764752 */ if (old_pip && (pip != old_pip || plun->lun_state & FCP_LUN_CHANGED)) { plun->lun_state &= ~(FCP_LUN_INIT); mutex_exit(&plun->lun_mutex); mutex_exit(&pptr->port_mutex); mutex_enter(&ptgt->tgt_mutex); (void) fcp_pass_to_hp(pptr, plun, CIP(old_pip), FCP_OFFLINE, lcount, tcount, NDI_DEVI_REMOVE, 0); mutex_exit(&ptgt->tgt_mutex); if (pip != NULL) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "Old pip=%p; New pip=%p don't match", old_pip, pip); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "Old pip=%p; New pip=NULL don't match", old_pip); } mutex_enter(&pptr->port_mutex); mutex_enter(&plun->lun_mutex); } /* * Since FC_WWN_SIZE is 8 bytes and its not like the * lun_guid_size which is dependent on the target, I don't * believe the same trancation happens here UNLESS the standards * change the FC_WWN_SIZE value to something larger than * MAXNAMELEN(currently 255 bytes). */ for (i = 0; i < FC_WWN_SIZE; i++) { (void) sprintf(&buf[i << 1], "%02x", ptgt->tgt_port_wwn.raw_wwn[i]); } (void) snprintf(uaddr, MAXNAMELEN, "w%s,%x", buf, plun->lun_num); if (pip == NULL || plun->lun_state & FCP_LUN_CHANGED) { /* * Release the locks before calling into * mdi_pi_alloc_compatible() since this can result in a * callback into fcp which can result in a deadlock * (see bug # 4870272). * * Basically, what we are trying to avoid is the scenario where * one thread does ndi_devi_enter() and tries to grab * fcp_mutex and another does it the other way round. * * But before we do that, make sure that nobody releases the * port in the meantime. We can do this by setting a flag. */ plun->lun_state &= ~(FCP_LUN_CHANGED); pptr->port_state |= FCP_STATE_IN_MDI; mutex_exit(&plun->lun_mutex); mutex_exit(&pptr->port_mutex); if (mdi_pi_alloc_compatible(pdip, nname, plun->lun_guid, uaddr, compatible, ncompatible, 0, &pip) != MDI_SUCCESS) { fcp_log(CE_WARN, pptr->port_dip, "!path alloc failed:0x%x", plun); mutex_enter(&pptr->port_mutex); mutex_enter(&plun->lun_mutex); pptr->port_state &= ~FCP_STATE_IN_MDI; failure++; goto end_of_fcp_create_pip; } mutex_enter(&pptr->port_mutex); mutex_enter(&plun->lun_mutex); pptr->port_state &= ~FCP_STATE_IN_MDI; } else { (void) mdi_prop_remove(pip, NULL); } mdi_pi_set_phci_private(pip, (caddr_t)plun); if (mdi_prop_update_byte_array(pip, NODE_WWN_PROP, ptgt->tgt_node_wwn.raw_wwn, FC_WWN_SIZE) != DDI_PROP_SUCCESS) { failure++; goto end_of_fcp_create_pip; } if (mdi_prop_update_byte_array(pip, PORT_WWN_PROP, ptgt->tgt_port_wwn.raw_wwn, FC_WWN_SIZE) != DDI_PROP_SUCCESS) { failure++; goto end_of_fcp_create_pip; } fcp_wwn_to_ascii(ptgt->tgt_port_wwn.raw_wwn, t_pwwn); t_pwwn[16] = '\0'; if (mdi_prop_update_string(pip, TGT_PORT_PROP, t_pwwn) != DDI_PROP_SUCCESS) { failure++; goto end_of_fcp_create_pip; } /* * If there is no hard address - We might have to deal with * that by using WWN - Having said that it is important to * recognize this problem early so ssd can be informed of * the right interconnect type. */ if (!FC_TOP_EXTERNAL(pptr->port_topology) && ptgt->tgt_hard_addr != 0) { tgt_id = (uint32_t) fcp_alpa_to_switch[ptgt->tgt_hard_addr]; } else { tgt_id = ptgt->tgt_d_id; } if (mdi_prop_update_int(pip, TARGET_PROP, tgt_id) != DDI_PROP_SUCCESS) { failure++; goto end_of_fcp_create_pip; } if (mdi_prop_update_int(pip, LUN_PROP, (int)plun->lun_num) != DDI_PROP_SUCCESS) { failure++; goto end_of_fcp_create_pip; } bcopy(&plun->lun_addr, &sam_lun, FCP_LUN_SIZE); if (mdi_prop_update_int64(pip, SAM_LUN_PROP, sam_lun) != DDI_PROP_SUCCESS) { failure++; goto end_of_fcp_create_pip; } end_of_fcp_create_pip: scsi_hba_nodename_compatible_free(nname, compatible); if (pip != NULL && failure) { (void) mdi_prop_remove(pip, NULL); mutex_exit(&plun->lun_mutex); mutex_exit(&pptr->port_mutex); (void) mdi_pi_free(pip, 0); mutex_enter(&pptr->port_mutex); mutex_enter(&plun->lun_mutex); pip = NULL; } return (pip); } static dev_info_t * fcp_find_existing_dip(struct fcp_lun *plun, dev_info_t *pdip, caddr_t name) { uint_t nbytes; uchar_t *bytes; uint_t nwords; uint32_t tgt_id; int *words; dev_info_t *cdip; dev_info_t *ndip; struct fcp_tgt *ptgt = plun->lun_tgt; struct fcp_port *pptr = ptgt->tgt_port; int circular; ndi_devi_enter(pdip, &circular); ndip = (dev_info_t *)DEVI(pdip)->devi_child; while ((cdip = ndip) != NULL) { ndip = (dev_info_t *)DEVI(cdip)->devi_sibling; if (strcmp(DEVI(cdip)->devi_node_name, name)) { continue; } if (ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, NODE_WWN_PROP, &bytes, &nbytes) != DDI_PROP_SUCCESS) { continue; } if (nbytes != FC_WWN_SIZE || bytes == NULL) { if (bytes != NULL) { ddi_prop_free(bytes); } continue; } ASSERT(bytes != NULL); if (bcmp(bytes, ptgt->tgt_node_wwn.raw_wwn, nbytes) != 0) { ddi_prop_free(bytes); continue; } ddi_prop_free(bytes); if (ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, PORT_WWN_PROP, &bytes, &nbytes) != DDI_PROP_SUCCESS) { continue; } if (nbytes != FC_WWN_SIZE || bytes == NULL) { if (bytes != NULL) { ddi_prop_free(bytes); } continue; } ASSERT(bytes != NULL); if (bcmp(bytes, ptgt->tgt_port_wwn.raw_wwn, nbytes) != 0) { ddi_prop_free(bytes); continue; } ddi_prop_free(bytes); if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, TARGET_PROP, &words, &nwords) != DDI_PROP_SUCCESS) { continue; } if (nwords != 1 || words == NULL) { if (words != NULL) { ddi_prop_free(words); } continue; } ASSERT(words != NULL); /* * If there is no hard address - We might have to deal with * that by using WWN - Having said that it is important to * recognize this problem early so ssd can be informed of * the right interconnect type. */ if (!FC_TOP_EXTERNAL(pptr->port_topology) && ptgt->tgt_hard_addr != 0) { tgt_id = (uint32_t)fcp_alpa_to_switch[ptgt->tgt_hard_addr]; } else { tgt_id = ptgt->tgt_d_id; } if (tgt_id != (uint32_t)*words) { ddi_prop_free(words); continue; } ddi_prop_free(words); if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, LUN_PROP, &words, &nwords) != DDI_PROP_SUCCESS) { continue; } if (nwords != 1 || words == NULL) { if (words != NULL) { ddi_prop_free(words); } continue; } ASSERT(words != NULL); if (plun->lun_num == (uint16_t)*words) { ddi_prop_free(words); break; } ddi_prop_free(words); } ndi_devi_exit(pdip, circular); return (cdip); } static int fcp_is_pip_present(struct fcp_lun *plun, mdi_pathinfo_t *pip) { dev_info_t *pdip; char buf[MAXNAMELEN]; char uaddr[MAXNAMELEN]; int rval = FC_FAILURE; ASSERT(MUTEX_HELD(&plun->lun_mutex)); pdip = plun->lun_tgt->tgt_port->port_dip; /* * Check if pip (and not plun->lun_cip) is NULL. plun->lun_cip can be * non-NULL even when the LUN is not there as in the case when a LUN is * configured and then deleted on the device end (for T3/T4 case). In * such cases, pip will be NULL. * * If the device generates an RSCN, it will end up getting offlined when * it disappeared and a new LUN will get created when it is rediscovered * on the device. If we check for lun_cip here, the LUN will not end * up getting onlined since this function will end up returning a * FC_SUCCESS. * * The behavior is different on other devices. For instance, on a HDS, * there was no RSCN generated by the device but the next I/O generated * a check condition and rediscovery got triggered that way. So, in * such cases, this path will not be exercised */ if (pip == NULL) { FCP_TRACE(fcp_logq, LUN_PORT->port_instbuf, fcp_trace, FCP_BUF_LEVEL_4, 0, "fcp_is_pip_present: plun->lun_cip is NULL: " "plun: %p lun state: %x num: %d target state: %x", plun, plun->lun_state, plun->lun_num, plun->lun_tgt->tgt_port->port_state); return (rval); } fcp_wwn_to_ascii(plun->lun_tgt->tgt_port_wwn.raw_wwn, buf); (void) snprintf(uaddr, MAXNAMELEN, "w%s,%x", buf, plun->lun_num); if (plun->lun_old_guid) { if (mdi_pi_find(pdip, plun->lun_old_guid, uaddr) == pip) { rval = FC_SUCCESS; } } else { if (mdi_pi_find(pdip, plun->lun_guid, uaddr) == pip) { rval = FC_SUCCESS; } } return (rval); } static mdi_pathinfo_t * fcp_find_existing_pip(struct fcp_lun *plun, dev_info_t *pdip) { char buf[MAXNAMELEN]; char uaddr[MAXNAMELEN]; mdi_pathinfo_t *pip; struct fcp_tgt *ptgt = plun->lun_tgt; struct fcp_port *pptr = ptgt->tgt_port; ASSERT(MUTEX_HELD(&pptr->port_mutex)); fcp_wwn_to_ascii(ptgt->tgt_port_wwn.raw_wwn, buf); (void) snprintf(uaddr, MAXNAMELEN, "w%s,%x", buf, plun->lun_num); pip = mdi_pi_find(pdip, plun->lun_guid, uaddr); return (pip); } static int fcp_online_child(struct fcp_lun *plun, child_info_t *cip, int lcount, int tcount, int flags, int *circ) { int rval; struct fcp_port *pptr = plun->lun_tgt->tgt_port; struct fcp_tgt *ptgt = plun->lun_tgt; dev_info_t *cdip = NULL; ASSERT(MUTEX_HELD(&pptr->port_mutex)); ASSERT(MUTEX_HELD(&plun->lun_mutex)); if (plun->lun_cip == NULL) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_online_child: plun->lun_cip is NULL: " "plun: %p state: %x num: %d target state: %x", plun, plun->lun_state, plun->lun_num, plun->lun_tgt->tgt_port->port_state); return (NDI_FAILURE); } again: if (plun->lun_mpxio == 0) { cdip = DIP(cip); mutex_exit(&plun->lun_mutex); mutex_exit(&pptr->port_mutex); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "!Invoking ndi_devi_online for %s: target=%x lun=%x", ddi_get_name(cdip), ptgt->tgt_d_id, plun->lun_num); /* * We could check for FCP_LUN_INIT here but chances * of getting here when it's already in FCP_LUN_INIT * is rare and a duplicate ndi_devi_online wouldn't * hurt either (as the node would already have been * in CF2) */ if (!i_ddi_devi_attached(ddi_get_parent(cdip))) { rval = ndi_devi_bind_driver(cdip, flags); } else { rval = ndi_devi_online(cdip, flags); } /* * We log the message into trace buffer if the device * is "ses" and into syslog for any other device * type. This is to prevent the ndi_devi_online failure * message that appears for V880/A5K ses devices. */ if (rval == NDI_SUCCESS) { mutex_enter(&ptgt->tgt_mutex); plun->lun_state |= FCP_LUN_INIT; mutex_exit(&ptgt->tgt_mutex); } else if (strncmp(ddi_node_name(cdip), "ses", 3) != 0) { fcp_log(CE_NOTE, pptr->port_dip, "!ndi_devi_online:" " failed for %s: target=%x lun=%x %x", ddi_get_name(cdip), ptgt->tgt_d_id, plun->lun_num, rval); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, " !ndi_devi_online:" " failed for %s: target=%x lun=%x %x", ddi_get_name(cdip), ptgt->tgt_d_id, plun->lun_num, rval); } } else { cdip = mdi_pi_get_client(PIP(cip)); mutex_exit(&plun->lun_mutex); mutex_exit(&pptr->port_mutex); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "!Invoking mdi_pi_online for %s: target=%x lun=%x", ddi_get_name(cdip), ptgt->tgt_d_id, plun->lun_num); /* * Hold path and exit phci to avoid deadlock with power * management code during mdi_pi_online. */ mdi_hold_path(PIP(cip)); mdi_devi_exit_phci(pptr->port_dip, *circ); rval = mdi_pi_online(PIP(cip), flags); mdi_devi_enter_phci(pptr->port_dip, circ); mdi_rele_path(PIP(cip)); if (rval == MDI_SUCCESS) { mutex_enter(&ptgt->tgt_mutex); plun->lun_state |= FCP_LUN_INIT; mutex_exit(&ptgt->tgt_mutex); /* * Clear MPxIO path permanent disable in case * fcp hotplug dropped the offline event. */ (void) mdi_pi_enable_path(PIP(cip), DRIVER_DISABLE); } else if (rval == MDI_NOT_SUPPORTED) { child_info_t *old_cip = cip; /* * MPxIO does not support this device yet. * Enumerate in legacy mode. */ mutex_enter(&pptr->port_mutex); mutex_enter(&plun->lun_mutex); plun->lun_mpxio = 0; plun->lun_cip = NULL; cdip = fcp_create_dip(plun, lcount, tcount); plun->lun_cip = cip = CIP(cdip); if (cip == NULL) { fcp_log(CE_WARN, pptr->port_dip, "!fcp_online_child: " "Create devinfo failed for LU=%p", plun); mutex_exit(&plun->lun_mutex); mutex_enter(&ptgt->tgt_mutex); plun->lun_state |= FCP_LUN_OFFLINE; mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); /* * free the mdi_pathinfo node */ (void) mdi_pi_free(PIP(old_cip), 0); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_online_child: creating devinfo " "node 0x%p for plun 0x%p", cip, plun); mutex_exit(&plun->lun_mutex); mutex_exit(&pptr->port_mutex); /* * free the mdi_pathinfo node */ (void) mdi_pi_free(PIP(old_cip), 0); mutex_enter(&pptr->port_mutex); mutex_enter(&plun->lun_mutex); goto again; } } else { if (cdip) { fcp_log(CE_NOTE, pptr->port_dip, "!fcp_online_child: mdi_pi_online:" " failed for %s: target=%x lun=%x %x", ddi_get_name(cdip), ptgt->tgt_d_id, plun->lun_num, rval); } } rval = (rval == MDI_SUCCESS) ? NDI_SUCCESS : NDI_FAILURE; } if (rval == NDI_SUCCESS) { if (cdip) { (void) ndi_event_retrieve_cookie( pptr->port_ndi_event_hdl, cdip, FCAL_INSERT_EVENT, &fcp_insert_eid, NDI_EVENT_NOPASS); (void) ndi_event_run_callbacks(pptr->port_ndi_event_hdl, cdip, fcp_insert_eid, NULL); } } mutex_enter(&pptr->port_mutex); mutex_enter(&plun->lun_mutex); return (rval); } /* ARGSUSED */ static int fcp_offline_child(struct fcp_lun *plun, child_info_t *cip, int lcount, int tcount, int flags, int *circ) { int rval; struct fcp_port *pptr = plun->lun_tgt->tgt_port; struct fcp_tgt *ptgt = plun->lun_tgt; dev_info_t *cdip; ASSERT(MUTEX_HELD(&plun->lun_mutex)); ASSERT(MUTEX_HELD(&pptr->port_mutex)); if (plun->lun_cip == NULL) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_offline_child: plun->lun_cip is NULL: " "plun: %p lun state: %x num: %d target state: %x", plun, plun->lun_state, plun->lun_num, plun->lun_tgt->tgt_port->port_state); return (NDI_FAILURE); } if (plun->lun_mpxio == 0) { cdip = DIP(cip); mutex_exit(&plun->lun_mutex); mutex_exit(&pptr->port_mutex); rval = ndi_devi_offline(DIP(cip), flags); if (rval != NDI_SUCCESS) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_offline_child: ndi_devi_offline failed " "rval=%x cip=%p", rval, cip); } } else { cdip = mdi_pi_get_client(PIP(cip)); mutex_exit(&plun->lun_mutex); mutex_exit(&pptr->port_mutex); /* * Exit phci to avoid deadlock with power management code * during mdi_pi_offline */ mdi_hold_path(PIP(cip)); mdi_devi_exit_phci(pptr->port_dip, *circ); rval = mdi_pi_offline(PIP(cip), flags); mdi_devi_enter_phci(pptr->port_dip, circ); mdi_rele_path(PIP(cip)); if (rval == MDI_SUCCESS) { /* * Clear MPxIO path permanent disable as the path is * already offlined. */ (void) mdi_pi_enable_path(PIP(cip), DRIVER_DISABLE); if (flags & NDI_DEVI_REMOVE) { (void) mdi_pi_free(PIP(cip), 0); } } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_offline_child: mdi_pi_offline failed " "rval=%x cip=%p", rval, cip); } rval = (rval == MDI_SUCCESS) ? NDI_SUCCESS : NDI_FAILURE; } mutex_enter(&ptgt->tgt_mutex); plun->lun_state &= ~FCP_LUN_INIT; mutex_exit(&ptgt->tgt_mutex); mutex_enter(&pptr->port_mutex); mutex_enter(&plun->lun_mutex); if (rval == NDI_SUCCESS) { cdip = NULL; if (flags & NDI_DEVI_REMOVE) { /* * If the guid of the LUN changes, lun_cip will not * equal to cip, and after offlining the LUN with the * old guid, we should keep lun_cip since it's the cip * of the LUN with the new guid. * Otherwise remove our reference to child node. */ if (plun->lun_cip == cip) { plun->lun_cip = NULL; } if (plun->lun_old_guid) { kmem_free(plun->lun_old_guid, plun->lun_old_guid_size); plun->lun_old_guid = NULL; plun->lun_old_guid_size = 0; } } } if (cdip) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "!%s failed for %s:" " target=%x lun=%x", "ndi_offline", ddi_get_name(cdip), ptgt->tgt_d_id, plun->lun_num); } return (rval); } static void fcp_remove_child(struct fcp_lun *plun) { ASSERT(MUTEX_HELD(&plun->lun_mutex)); if (fcp_is_child_present(plun, plun->lun_cip) == FC_SUCCESS) { if (plun->lun_mpxio == 0) { (void) ndi_prop_remove_all(DIP(plun->lun_cip)); (void) ndi_devi_free(DIP(plun->lun_cip)); } else { mutex_exit(&plun->lun_mutex); mutex_exit(&plun->lun_tgt->tgt_mutex); mutex_exit(&plun->lun_tgt->tgt_port->port_mutex); FCP_TRACE(fcp_logq, plun->lun_tgt->tgt_port->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "lun=%p pip freed %p", plun, plun->lun_cip); (void) mdi_prop_remove(PIP(plun->lun_cip), NULL); (void) mdi_pi_free(PIP(plun->lun_cip), 0); mutex_enter(&plun->lun_tgt->tgt_port->port_mutex); mutex_enter(&plun->lun_tgt->tgt_mutex); mutex_enter(&plun->lun_mutex); } } plun->lun_cip = NULL; } /* * called when a timeout occurs * * can be scheduled during an attach or resume (if not already running) * * one timeout is set up for all ports * * acquires and releases the global mutex */ /*ARGSUSED*/ static void fcp_watch(void *arg) { struct fcp_port *pptr; struct fcp_ipkt *icmd; struct fcp_ipkt *nicmd; struct fcp_pkt *cmd; struct fcp_pkt *ncmd; struct fcp_pkt *tail; struct fcp_pkt *pcmd; struct fcp_pkt *save_head; struct fcp_port *save_port; /* increment global watchdog time */ fcp_watchdog_time += fcp_watchdog_timeout; mutex_enter(&fcp_global_mutex); /* scan each port in our list */ for (pptr = fcp_port_head; pptr != NULL; pptr = pptr->port_next) { save_port = fcp_port_head; pptr->port_state |= FCP_STATE_IN_WATCHDOG; mutex_exit(&fcp_global_mutex); mutex_enter(&pptr->port_mutex); if (pptr->port_ipkt_list == NULL && (pptr->port_state & (FCP_STATE_SUSPENDED | FCP_STATE_DETACHING | FCP_STATE_POWER_DOWN))) { pptr->port_state &= ~FCP_STATE_IN_WATCHDOG; mutex_exit(&pptr->port_mutex); mutex_enter(&fcp_global_mutex); goto end_of_watchdog; } /* * We check if a list of targets need to be offlined. */ if (pptr->port_offline_tgts) { fcp_scan_offline_tgts(pptr); } /* * We check if a list of luns need to be offlined. */ if (pptr->port_offline_luns) { fcp_scan_offline_luns(pptr); } /* * We check if a list of targets or luns need to be reset. */ if (pptr->port_reset_list) { fcp_check_reset_delay(pptr); } mutex_exit(&pptr->port_mutex); /* * This is where the pending commands (pkt) are checked for * timeout. */ mutex_enter(&pptr->port_pkt_mutex); tail = pptr->port_pkt_tail; for (pcmd = NULL, cmd = pptr->port_pkt_head; cmd != NULL; cmd = ncmd) { ncmd = cmd->cmd_next; /* * If a command is in this queue the bit CFLAG_IN_QUEUE * must be set. */ ASSERT(cmd->cmd_flags & CFLAG_IN_QUEUE); /* * FCP_INVALID_TIMEOUT will be set for those * command that need to be failed. Mostly those * cmds that could not be queued down for the * "timeout" value. cmd->cmd_timeout is used * to try and requeue the command regularly. */ if (cmd->cmd_timeout >= fcp_watchdog_time) { /* * This command hasn't timed out yet. Let's * go to the next one. */ pcmd = cmd; goto end_of_loop; } if (cmd == pptr->port_pkt_head) { ASSERT(pcmd == NULL); pptr->port_pkt_head = cmd->cmd_next; } else { ASSERT(pcmd != NULL); pcmd->cmd_next = cmd->cmd_next; } if (cmd == pptr->port_pkt_tail) { ASSERT(cmd->cmd_next == NULL); pptr->port_pkt_tail = pcmd; if (pcmd) { pcmd->cmd_next = NULL; } } cmd->cmd_next = NULL; /* * save the current head before dropping the * mutex - If the head doesn't remain the * same after re acquiring the mutex, just * bail out and revisit on next tick. * * PS: The tail pointer can change as the commands * get requeued after failure to retransport */ save_head = pptr->port_pkt_head; mutex_exit(&pptr->port_pkt_mutex); if (cmd->cmd_fp_pkt->pkt_timeout == FCP_INVALID_TIMEOUT) { struct scsi_pkt *pkt = cmd->cmd_pkt; struct fcp_lun *plun; struct fcp_tgt *ptgt; plun = ADDR2LUN(&pkt->pkt_address); ptgt = plun->lun_tgt; FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "SCSI cmd 0x%x to D_ID=%x timed out", pkt->pkt_cdbp[0], ptgt->tgt_d_id); cmd->cmd_state == FCP_PKT_ABORTING ? fcp_fail_cmd(cmd, CMD_RESET, STAT_DEV_RESET) : fcp_fail_cmd(cmd, CMD_TIMEOUT, STAT_ABORTED); } else { fcp_retransport_cmd(pptr, cmd); } mutex_enter(&pptr->port_pkt_mutex); if (save_head && save_head != pptr->port_pkt_head) { /* * Looks like linked list got changed (mostly * happens when an an OFFLINE LUN code starts * returning overflow queue commands in * parallel. So bail out and revisit during * next tick */ break; } end_of_loop: /* * Scan only upto the previously known tail pointer * to avoid excessive processing - lots of new packets * could have been added to the tail or the old ones * re-queued. */ if (cmd == tail) { break; } } mutex_exit(&pptr->port_pkt_mutex); mutex_enter(&pptr->port_mutex); for (icmd = pptr->port_ipkt_list; icmd != NULL; icmd = nicmd) { struct fcp_tgt *ptgt = icmd->ipkt_tgt; nicmd = icmd->ipkt_next; if ((icmd->ipkt_restart != 0) && (icmd->ipkt_restart >= fcp_watchdog_time)) { /* packet has not timed out */ continue; } /* time for packet re-transport */ if (icmd == pptr->port_ipkt_list) { pptr->port_ipkt_list = icmd->ipkt_next; if (pptr->port_ipkt_list) { pptr->port_ipkt_list->ipkt_prev = NULL; } } else { icmd->ipkt_prev->ipkt_next = icmd->ipkt_next; if (icmd->ipkt_next) { icmd->ipkt_next->ipkt_prev = icmd->ipkt_prev; } } icmd->ipkt_next = NULL; icmd->ipkt_prev = NULL; mutex_exit(&pptr->port_mutex); if (fcp_is_retryable(icmd)) { fc_ulp_rscn_info_t *rscnp = (fc_ulp_rscn_info_t *)icmd->ipkt_fpkt-> pkt_ulp_rscn_infop; FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "%x to D_ID=%x Retrying..", icmd->ipkt_opcode, icmd->ipkt_fpkt->pkt_cmd_fhdr.d_id); /* * Update the RSCN count in the packet * before resending. */ if (rscnp != NULL) { rscnp->ulp_rscn_count = fc_ulp_get_rscn_count(pptr-> port_fp_handle); } mutex_enter(&pptr->port_mutex); mutex_enter(&ptgt->tgt_mutex); if (!FCP_STATE_CHANGED(pptr, ptgt, icmd)) { mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); switch (icmd->ipkt_opcode) { int rval; case LA_ELS_PLOGI: if ((rval = fc_ulp_login( pptr->port_fp_handle, &icmd->ipkt_fpkt, 1)) == FC_SUCCESS) { mutex_enter( &pptr->port_mutex); continue; } if (fcp_handle_ipkt_errors( pptr, ptgt, icmd, rval, "PLOGI") == DDI_SUCCESS) { mutex_enter( &pptr->port_mutex); continue; } break; case LA_ELS_PRLI: if ((rval = fc_ulp_issue_els( pptr->port_fp_handle, icmd->ipkt_fpkt)) == FC_SUCCESS) { mutex_enter( &pptr->port_mutex); continue; } if (fcp_handle_ipkt_errors( pptr, ptgt, icmd, rval, "PRLI") == DDI_SUCCESS) { mutex_enter( &pptr->port_mutex); continue; } break; default: if ((rval = fcp_transport( pptr->port_fp_handle, icmd->ipkt_fpkt, 1)) == FC_SUCCESS) { mutex_enter( &pptr->port_mutex); continue; } if (fcp_handle_ipkt_errors( pptr, ptgt, icmd, rval, "PRLI") == DDI_SUCCESS) { mutex_enter( &pptr->port_mutex); continue; } break; } } else { mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); } } else { fcp_print_error(icmd->ipkt_fpkt); } (void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); fcp_icmd_free(pptr, icmd); mutex_enter(&pptr->port_mutex); } pptr->port_state &= ~FCP_STATE_IN_WATCHDOG; mutex_exit(&pptr->port_mutex); mutex_enter(&fcp_global_mutex); end_of_watchdog: /* * Bail out early before getting into trouble */ if (save_port != fcp_port_head) { break; } } if (fcp_watchdog_init > 0) { /* reschedule timeout to go again */ fcp_watchdog_id = timeout(fcp_watch, NULL, fcp_watchdog_tick); } mutex_exit(&fcp_global_mutex); } static void fcp_check_reset_delay(struct fcp_port *pptr) { uint32_t tgt_cnt; int level; struct fcp_tgt *ptgt; struct fcp_lun *plun; struct fcp_reset_elem *cur = NULL; struct fcp_reset_elem *next = NULL; struct fcp_reset_elem *prev = NULL; ASSERT(mutex_owned(&pptr->port_mutex)); next = pptr->port_reset_list; while ((cur = next) != NULL) { next = cur->next; if (cur->timeout < fcp_watchdog_time) { prev = cur; continue; } ptgt = cur->tgt; plun = cur->lun; tgt_cnt = cur->tgt_cnt; if (ptgt) { level = RESET_TARGET; } else { ASSERT(plun != NULL); level = RESET_LUN; ptgt = plun->lun_tgt; } if (prev) { prev->next = next; } else { /* * Because we drop port mutex while doing aborts for * packets, we can't rely on reset_list pointing to * our head */ if (cur == pptr->port_reset_list) { pptr->port_reset_list = next; } else { struct fcp_reset_elem *which; which = pptr->port_reset_list; while (which && which->next != cur) { which = which->next; } ASSERT(which != NULL); which->next = next; prev = which; } } kmem_free(cur, sizeof (*cur)); if (tgt_cnt == ptgt->tgt_change_cnt) { mutex_enter(&ptgt->tgt_mutex); if (level == RESET_TARGET) { fcp_update_tgt_state(ptgt, FCP_RESET, FCP_LUN_BUSY); } else { fcp_update_lun_state(plun, FCP_RESET, FCP_LUN_BUSY); } mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); fcp_abort_all(pptr, ptgt, plun, tgt_cnt); mutex_enter(&pptr->port_mutex); } } } static void fcp_abort_all(struct fcp_port *pptr, struct fcp_tgt *ttgt, struct fcp_lun *rlun, int tgt_cnt) { int rval; struct fcp_lun *tlun, *nlun; struct fcp_pkt *pcmd = NULL, *ncmd = NULL, *cmd = NULL, *head = NULL, *tail = NULL; mutex_enter(&pptr->port_pkt_mutex); for (cmd = pptr->port_pkt_head; cmd != NULL; cmd = ncmd) { struct fcp_lun *plun = ADDR2LUN(&cmd->cmd_pkt->pkt_address); struct fcp_tgt *ptgt = plun->lun_tgt; ncmd = cmd->cmd_next; if (ptgt != ttgt && plun != rlun) { pcmd = cmd; continue; } if (pcmd != NULL) { ASSERT(pptr->port_pkt_head != cmd); pcmd->cmd_next = ncmd; } else { ASSERT(cmd == pptr->port_pkt_head); pptr->port_pkt_head = ncmd; } if (pptr->port_pkt_tail == cmd) { ASSERT(cmd->cmd_next == NULL); pptr->port_pkt_tail = pcmd; if (pcmd != NULL) { pcmd->cmd_next = NULL; } } if (head == NULL) { head = tail = cmd; } else { ASSERT(tail != NULL); tail->cmd_next = cmd; tail = cmd; } cmd->cmd_next = NULL; } mutex_exit(&pptr->port_pkt_mutex); for (cmd = head; cmd != NULL; cmd = ncmd) { struct scsi_pkt *pkt = cmd->cmd_pkt; ncmd = cmd->cmd_next; ASSERT(pkt != NULL); mutex_enter(&pptr->port_mutex); if (ttgt->tgt_change_cnt == tgt_cnt) { mutex_exit(&pptr->port_mutex); cmd->cmd_flags &= ~CFLAG_IN_QUEUE; pkt->pkt_reason = CMD_RESET; pkt->pkt_statistics |= STAT_DEV_RESET; cmd->cmd_state = FCP_PKT_IDLE; fcp_post_callback(cmd); } else { mutex_exit(&pptr->port_mutex); } } /* * If the FCA will return all the commands in its queue then our * work is easy, just return. */ if (pptr->port_reset_action == FC_RESET_RETURN_ALL) { return; } /* * For RESET_LUN get hold of target pointer */ if (ttgt == NULL) { ASSERT(rlun != NULL); ttgt = rlun->lun_tgt; ASSERT(ttgt != NULL); } /* * There are some severe race conditions here. * While we are trying to abort the pkt, it might be completing * so mark it aborted and if the abort does not succeed then * handle it in the watch thread. */ mutex_enter(&ttgt->tgt_mutex); nlun = ttgt->tgt_lun; mutex_exit(&ttgt->tgt_mutex); while ((tlun = nlun) != NULL) { int restart = 0; if (rlun && rlun != tlun) { mutex_enter(&ttgt->tgt_mutex); nlun = tlun->lun_next; mutex_exit(&ttgt->tgt_mutex); continue; } mutex_enter(&tlun->lun_mutex); cmd = tlun->lun_pkt_head; while (cmd != NULL) { if (cmd->cmd_state == FCP_PKT_ISSUED) { struct scsi_pkt *pkt; restart = 1; cmd->cmd_state = FCP_PKT_ABORTING; mutex_exit(&tlun->lun_mutex); rval = fc_ulp_abort(pptr->port_fp_handle, cmd->cmd_fp_pkt, KM_SLEEP); if (rval == FC_SUCCESS) { pkt = cmd->cmd_pkt; pkt->pkt_reason = CMD_RESET; pkt->pkt_statistics |= STAT_DEV_RESET; cmd->cmd_state = FCP_PKT_IDLE; fcp_post_callback(cmd); } else { caddr_t msg; (void) fc_ulp_error(rval, &msg); /* * This part is tricky. The abort * failed and now the command could * be completing. The cmd_state == * FCP_PKT_ABORTING should save * us in fcp_cmd_callback. If we * are already aborting ignore the * command in fcp_cmd_callback. * Here we leave this packet for 20 * sec to be aborted in the * fcp_watch thread. */ fcp_log(CE_WARN, pptr->port_dip, "!Abort failed after reset %s", msg); cmd->cmd_timeout = fcp_watchdog_time + cmd->cmd_pkt->pkt_time + FCP_FAILED_DELAY; cmd->cmd_fp_pkt->pkt_timeout = FCP_INVALID_TIMEOUT; /* * This is a hack, cmd is put in the * overflow queue so that it can be * timed out finally */ cmd->cmd_flags |= CFLAG_IN_QUEUE; mutex_enter(&pptr->port_pkt_mutex); if (pptr->port_pkt_head) { ASSERT(pptr->port_pkt_tail != NULL); pptr->port_pkt_tail->cmd_next = cmd; pptr->port_pkt_tail = cmd; } else { ASSERT(pptr->port_pkt_tail == NULL); pptr->port_pkt_head = pptr->port_pkt_tail = cmd; } cmd->cmd_next = NULL; mutex_exit(&pptr->port_pkt_mutex); } mutex_enter(&tlun->lun_mutex); cmd = tlun->lun_pkt_head; } else { cmd = cmd->cmd_forw; } } mutex_exit(&tlun->lun_mutex); mutex_enter(&ttgt->tgt_mutex); restart == 1 ? (nlun = ttgt->tgt_lun) : (nlun = tlun->lun_next); mutex_exit(&ttgt->tgt_mutex); mutex_enter(&pptr->port_mutex); if (tgt_cnt != ttgt->tgt_change_cnt) { mutex_exit(&pptr->port_mutex); return; } else { mutex_exit(&pptr->port_mutex); } } } /* * unlink the soft state, returning the soft state found (if any) * * acquires and releases the global mutex */ struct fcp_port * fcp_soft_state_unlink(struct fcp_port *pptr) { struct fcp_port *hptr; /* ptr index */ struct fcp_port *tptr; /* prev hptr */ mutex_enter(&fcp_global_mutex); for (hptr = fcp_port_head, tptr = NULL; hptr != NULL; tptr = hptr, hptr = hptr->port_next) { if (hptr == pptr) { /* we found a match -- remove this item */ if (tptr == NULL) { /* we're at the head of the list */ fcp_port_head = hptr->port_next; } else { tptr->port_next = hptr->port_next; } break; /* success */ } } if (fcp_port_head == NULL) { fcp_cleanup_blacklist(&fcp_lun_blacklist); } mutex_exit(&fcp_global_mutex); return (hptr); } /* * called by fcp_scsi_hba_tgt_init to find a LUN given a * WWN and a LUN number */ /* ARGSUSED */ static struct fcp_lun * fcp_lookup_lun(struct fcp_port *pptr, uchar_t *wwn, uint16_t lun) { int hash; struct fcp_tgt *ptgt; struct fcp_lun *plun; ASSERT(mutex_owned(&pptr->port_mutex)); hash = FCP_HASH(wwn); for (ptgt = pptr->port_tgt_hash_table[hash]; ptgt != NULL; ptgt = ptgt->tgt_next) { if (bcmp((caddr_t)wwn, (caddr_t)&ptgt->tgt_port_wwn.raw_wwn[0], sizeof (ptgt->tgt_port_wwn)) == 0) { mutex_enter(&ptgt->tgt_mutex); for (plun = ptgt->tgt_lun; plun != NULL; plun = plun->lun_next) { if (plun->lun_num == lun) { mutex_exit(&ptgt->tgt_mutex); return (plun); } } mutex_exit(&ptgt->tgt_mutex); return (NULL); } } return (NULL); } /* * Function: fcp_prepare_pkt * * Description: This function prepares the SCSI cmd pkt, passed by the caller, * for fcp_start(). It binds the data or partially maps it. * Builds the FCP header and starts the initialization of the * Fibre Channel header. * * Argument: *pptr FCP port. * *cmd FCP packet. * *plun LUN the command will be sent to. * * Context: User, Kernel and Interrupt context. */ static void fcp_prepare_pkt(struct fcp_port *pptr, struct fcp_pkt *cmd, struct fcp_lun *plun) { fc_packet_t *fpkt = cmd->cmd_fp_pkt; struct fcp_tgt *ptgt = plun->lun_tgt; struct fcp_cmd *fcmd = &cmd->cmd_fcp_cmd; ASSERT(cmd->cmd_pkt->pkt_comp || (cmd->cmd_pkt->pkt_flags & FLAG_NOINTR)); if (cmd->cmd_pkt->pkt_numcookies) { if (cmd->cmd_pkt->pkt_dma_flags & DDI_DMA_READ) { fcmd->fcp_cntl.cntl_read_data = 1; fcmd->fcp_cntl.cntl_write_data = 0; fpkt->pkt_tran_type = FC_PKT_FCP_READ; } else { fcmd->fcp_cntl.cntl_read_data = 0; fcmd->fcp_cntl.cntl_write_data = 1; fpkt->pkt_tran_type = FC_PKT_FCP_WRITE; } fpkt->pkt_data_cookie = cmd->cmd_pkt->pkt_cookies; fpkt->pkt_data_cookie_cnt = cmd->cmd_pkt->pkt_numcookies; ASSERT(fpkt->pkt_data_cookie_cnt <= pptr->port_data_dma_attr.dma_attr_sgllen); cmd->cmd_dmacount = cmd->cmd_pkt->pkt_dma_len; /* FCA needs pkt_datalen to be set */ fpkt->pkt_datalen = cmd->cmd_dmacount; fcmd->fcp_data_len = cmd->cmd_dmacount; } else { fcmd->fcp_cntl.cntl_read_data = 0; fcmd->fcp_cntl.cntl_write_data = 0; fpkt->pkt_tran_type = FC_PKT_EXCHANGE; fpkt->pkt_datalen = 0; fcmd->fcp_data_len = 0; } /* set up the Tagged Queuing type */ if (cmd->cmd_pkt->pkt_flags & FLAG_HTAG) { fcmd->fcp_cntl.cntl_qtype = FCP_QTYPE_HEAD_OF_Q; } else if (cmd->cmd_pkt->pkt_flags & FLAG_OTAG) { fcmd->fcp_cntl.cntl_qtype = FCP_QTYPE_ORDERED; } else if (cmd->cmd_pkt->pkt_flags & FLAG_STAG) { fcmd->fcp_cntl.cntl_qtype = FCP_QTYPE_SIMPLE; } else { fcmd->fcp_cntl.cntl_qtype = FCP_QTYPE_UNTAGGED; } fcmd->fcp_ent_addr = plun->lun_addr; if (pptr->port_fcp_dma != FC_NO_DVMA_SPACE) { FCP_CP_OUT((uint8_t *)fcmd, fpkt->pkt_cmd, fpkt->pkt_cmd_acc, sizeof (struct fcp_cmd)); } else { ASSERT(fpkt->pkt_cmd_dma == NULL && fpkt->pkt_resp_dma == NULL); } cmd->cmd_pkt->pkt_reason = CMD_CMPLT; cmd->cmd_pkt->pkt_state = 0; cmd->cmd_pkt->pkt_statistics = 0; cmd->cmd_pkt->pkt_resid = 0; cmd->cmd_fp_pkt->pkt_data_dma = cmd->cmd_pkt->pkt_handle; if (cmd->cmd_pkt->pkt_flags & FLAG_NOINTR) { fpkt->pkt_tran_flags = (FC_TRAN_CLASS3 | FC_TRAN_NO_INTR); fpkt->pkt_comp = NULL; } else { fpkt->pkt_tran_flags = (FC_TRAN_CLASS3 | FC_TRAN_INTR); if (cmd->cmd_pkt->pkt_flags & FLAG_IMMEDIATE_CB) { fpkt->pkt_tran_flags |= FC_TRAN_IMMEDIATE_CB; } fpkt->pkt_comp = fcp_cmd_callback; } mutex_enter(&pptr->port_mutex); if (pptr->port_state & FCP_STATE_SUSPENDED) { fpkt->pkt_tran_flags |= FC_TRAN_DUMPING; } mutex_exit(&pptr->port_mutex); fpkt->pkt_cmd_fhdr.d_id = ptgt->tgt_d_id; fpkt->pkt_cmd_fhdr.s_id = pptr->port_id; /* * Save a few kernel cycles here */ #ifndef __lock_lint fpkt->pkt_fca_device = ptgt->tgt_fca_dev; #endif /* __lock_lint */ } static void fcp_post_callback(struct fcp_pkt *cmd) { scsi_hba_pkt_comp(cmd->cmd_pkt); } /* * called to do polled I/O by fcp_start() * * return a transport status value, i.e. TRAN_ACCECPT for success */ static int fcp_dopoll(struct fcp_port *pptr, struct fcp_pkt *cmd) { int rval; #ifdef DEBUG mutex_enter(&pptr->port_pkt_mutex); pptr->port_npkts++; mutex_exit(&pptr->port_pkt_mutex); #endif /* DEBUG */ if (cmd->cmd_fp_pkt->pkt_timeout) { cmd->cmd_fp_pkt->pkt_timeout = cmd->cmd_pkt->pkt_time; } else { cmd->cmd_fp_pkt->pkt_timeout = FCP_POLL_TIMEOUT; } ASSERT(cmd->cmd_fp_pkt->pkt_comp == NULL); cmd->cmd_state = FCP_PKT_ISSUED; rval = fc_ulp_transport(pptr->port_fp_handle, cmd->cmd_fp_pkt); #ifdef DEBUG mutex_enter(&pptr->port_pkt_mutex); pptr->port_npkts--; mutex_exit(&pptr->port_pkt_mutex); #endif /* DEBUG */ cmd->cmd_state = FCP_PKT_IDLE; switch (rval) { case FC_SUCCESS: if (cmd->cmd_fp_pkt->pkt_state == FC_PKT_SUCCESS) { fcp_complete_pkt(cmd->cmd_fp_pkt); rval = TRAN_ACCEPT; } else { rval = TRAN_FATAL_ERROR; } break; case FC_TRAN_BUSY: rval = TRAN_BUSY; cmd->cmd_pkt->pkt_resid = 0; break; case FC_BADPACKET: rval = TRAN_BADPKT; break; default: rval = TRAN_FATAL_ERROR; break; } return (rval); } /* * called by some of the following transport-called routines to convert * a supplied dip ptr to a port struct ptr (i.e. to the soft state) */ static struct fcp_port * fcp_dip2port(dev_info_t *dip) { int instance; instance = ddi_get_instance(dip); return (ddi_get_soft_state(fcp_softstate, instance)); } /* * called internally to return a LUN given a dip */ struct fcp_lun * fcp_get_lun_from_cip(struct fcp_port *pptr, child_info_t *cip) { struct fcp_tgt *ptgt; struct fcp_lun *plun; int i; ASSERT(mutex_owned(&pptr->port_mutex)); for (i = 0; i < FCP_NUM_HASH; i++) { for (ptgt = pptr->port_tgt_hash_table[i]; ptgt != NULL; ptgt = ptgt->tgt_next) { mutex_enter(&ptgt->tgt_mutex); for (plun = ptgt->tgt_lun; plun != NULL; plun = plun->lun_next) { mutex_enter(&plun->lun_mutex); if (plun->lun_cip == cip) { mutex_exit(&plun->lun_mutex); mutex_exit(&ptgt->tgt_mutex); return (plun); /* match found */ } mutex_exit(&plun->lun_mutex); } mutex_exit(&ptgt->tgt_mutex); } } return (NULL); /* no LUN found */ } /* * pass an element to the hotplug list, kick the hotplug thread * and wait for the element to get processed by the hotplug thread. * on return the element is freed. * * return zero success and non-zero on failure * * acquires/releases the target mutex * */ static int fcp_pass_to_hp_and_wait(struct fcp_port *pptr, struct fcp_lun *plun, child_info_t *cip, int what, int link_cnt, int tgt_cnt, int flags) { struct fcp_hp_elem *elem; int rval; mutex_enter(&plun->lun_tgt->tgt_mutex); if ((elem = fcp_pass_to_hp(pptr, plun, cip, what, link_cnt, tgt_cnt, flags, 1)) == NULL) { mutex_exit(&plun->lun_tgt->tgt_mutex); fcp_log(CE_CONT, pptr->port_dip, "Can not pass_to_hp: what: %d; D_ID=%x, LUN=%x\n", what, plun->lun_tgt->tgt_d_id, plun->lun_num); return (NDI_FAILURE); } mutex_exit(&plun->lun_tgt->tgt_mutex); mutex_enter(&elem->mutex); if (elem->wait) { while (elem->wait) { cv_wait(&elem->cv, &elem->mutex); } } rval = (elem->result); mutex_exit(&elem->mutex); mutex_destroy(&elem->mutex); cv_destroy(&elem->cv); kmem_free(elem, sizeof (struct fcp_hp_elem)); return (rval); } /* * pass an element to the hotplug list, and then * kick the hotplug thread * * return Boolean success, i.e. non-zero if all goes well, else zero on error * * acquires/releases the hotplug mutex * * called with the target mutex owned * * memory acquired in NOSLEEP mode * NOTE: if wait is set to 1 then the caller is responsible for waiting on * for the hp daemon to process the request and is responsible for * freeing the element */ static struct fcp_hp_elem * fcp_pass_to_hp(struct fcp_port *pptr, struct fcp_lun *plun, child_info_t *cip, int what, int link_cnt, int tgt_cnt, int flags, int wait) { struct fcp_hp_elem *elem; dev_info_t *pdip; ASSERT(pptr != NULL); ASSERT(plun != NULL); ASSERT(plun->lun_tgt != NULL); ASSERT(mutex_owned(&plun->lun_tgt->tgt_mutex)); /* create space for a hotplug element */ if ((elem = kmem_zalloc(sizeof (struct fcp_hp_elem), KM_NOSLEEP)) == NULL) { fcp_log(CE_WARN, NULL, "!can't allocate memory for hotplug element"); return (NULL); } /* fill in hotplug element */ elem->port = pptr; elem->lun = plun; elem->cip = cip; elem->old_lun_mpxio = plun->lun_mpxio; elem->what = what; elem->flags = flags; elem->link_cnt = link_cnt; elem->tgt_cnt = tgt_cnt; elem->wait = wait; mutex_init(&elem->mutex, NULL, MUTEX_DRIVER, NULL); cv_init(&elem->cv, NULL, CV_DRIVER, NULL); /* schedule the hotplug task */ pdip = pptr->port_dip; mutex_enter(&plun->lun_mutex); if (elem->what == FCP_ONLINE || elem->what == FCP_OFFLINE) { plun->lun_event_count++; elem->event_cnt = plun->lun_event_count; } mutex_exit(&plun->lun_mutex); if (taskq_dispatch(DEVI(pdip)->devi_taskq, fcp_hp_task, (void *)elem, KM_NOSLEEP) == NULL) { mutex_enter(&plun->lun_mutex); if (elem->what == FCP_ONLINE || elem->what == FCP_OFFLINE) { plun->lun_event_count--; } mutex_exit(&plun->lun_mutex); kmem_free(elem, sizeof (*elem)); return (0); } return (elem); } static void fcp_retransport_cmd(struct fcp_port *pptr, struct fcp_pkt *cmd) { int rval; struct scsi_address *ap; struct fcp_lun *plun; struct fcp_tgt *ptgt; fc_packet_t *fpkt; ap = &cmd->cmd_pkt->pkt_address; plun = ADDR2LUN(ap); ptgt = plun->lun_tgt; ASSERT(cmd->cmd_flags & CFLAG_IN_QUEUE); cmd->cmd_state = FCP_PKT_IDLE; mutex_enter(&pptr->port_mutex); mutex_enter(&ptgt->tgt_mutex); if (((plun->lun_state & (FCP_LUN_BUSY | FCP_LUN_OFFLINE)) == 0) && (!(pptr->port_state & FCP_STATE_ONLINING))) { fc_ulp_rscn_info_t *rscnp; cmd->cmd_state = FCP_PKT_ISSUED; /* * It is possible for pkt_pd to be NULL if tgt_pd_handle was * originally NULL, hence we try to set it to the pd pointed * to by the SCSI device we're trying to get to. */ fpkt = cmd->cmd_fp_pkt; if ((fpkt->pkt_pd == NULL) && (ptgt->tgt_pd_handle != NULL)) { fpkt->pkt_pd = ptgt->tgt_pd_handle; /* * We need to notify the transport that we now have a * reference to the remote port handle. */ fc_ulp_hold_remote_port(ptgt->tgt_pd_handle); } mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); ASSERT((cmd->cmd_pkt->pkt_flags & FLAG_NOINTR) == 0); /* prepare the packet */ fcp_prepare_pkt(pptr, cmd, plun); rscnp = (fc_ulp_rscn_info_t *)cmd->cmd_fp_pkt-> pkt_ulp_rscn_infop; cmd->cmd_timeout = cmd->cmd_pkt->pkt_time ? fcp_watchdog_time + cmd->cmd_pkt->pkt_time : 0; if (rscnp != NULL) { rscnp->ulp_rscn_count = fc_ulp_get_rscn_count(pptr-> port_fp_handle); } rval = fcp_transport(pptr->port_fp_handle, cmd->cmd_fp_pkt, 0); if (rval == FC_SUCCESS) { return; } cmd->cmd_state &= ~FCP_PKT_ISSUED; } else { mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); } fcp_queue_pkt(pptr, cmd); } static void fcp_fail_cmd(struct fcp_pkt *cmd, uchar_t reason, uint_t statistics) { ASSERT(cmd->cmd_flags & CFLAG_IN_QUEUE); cmd->cmd_flags &= ~CFLAG_IN_QUEUE; cmd->cmd_state = FCP_PKT_IDLE; cmd->cmd_pkt->pkt_reason = reason; cmd->cmd_pkt->pkt_state = 0; cmd->cmd_pkt->pkt_statistics = statistics; fcp_post_callback(cmd); } /* * Function: fcp_queue_pkt * * Description: This function queues the packet passed by the caller into * the list of packets of the FCP port. * * Argument: *pptr FCP port. * *cmd FCP packet to queue. * * Return Value: None * * Context: User, Kernel and Interrupt context. */ static void fcp_queue_pkt(struct fcp_port *pptr, struct fcp_pkt *cmd) { ASSERT((cmd->cmd_pkt->pkt_flags & FLAG_NOQUEUE) == NULL); mutex_enter(&pptr->port_pkt_mutex); cmd->cmd_flags |= CFLAG_IN_QUEUE; ASSERT(cmd->cmd_state != FCP_PKT_ISSUED); cmd->cmd_timeout = fcp_watchdog_time + FCP_QUEUE_DELAY; /* * zero pkt_time means hang around for ever */ if (cmd->cmd_pkt->pkt_time) { if (cmd->cmd_fp_pkt->pkt_timeout > FCP_QUEUE_DELAY) { cmd->cmd_fp_pkt->pkt_timeout -= FCP_QUEUE_DELAY; } else { /* * Indicate the watch thread to fail the * command by setting it to highest value */ cmd->cmd_timeout = fcp_watchdog_time; cmd->cmd_fp_pkt->pkt_timeout = FCP_INVALID_TIMEOUT; } } if (pptr->port_pkt_head) { ASSERT(pptr->port_pkt_tail != NULL); pptr->port_pkt_tail->cmd_next = cmd; pptr->port_pkt_tail = cmd; } else { ASSERT(pptr->port_pkt_tail == NULL); pptr->port_pkt_head = pptr->port_pkt_tail = cmd; } cmd->cmd_next = NULL; mutex_exit(&pptr->port_pkt_mutex); } /* * Function: fcp_update_targets * * Description: This function applies the specified change of state to all * the targets listed. The operation applied is 'set'. * * Argument: *pptr FCP port. * *dev_list Array of fc_portmap_t structures. * count Length of dev_list. * state State bits to update. * cause Reason for the update. * * Return Value: None * * Context: User, Kernel and Interrupt context. * The mutex pptr->port_mutex must be held. */ static void fcp_update_targets(struct fcp_port *pptr, fc_portmap_t *dev_list, uint32_t count, uint32_t state, int cause) { fc_portmap_t *map_entry; struct fcp_tgt *ptgt; ASSERT(MUTEX_HELD(&pptr->port_mutex)); while (count--) { map_entry = &(dev_list[count]); ptgt = fcp_lookup_target(pptr, (uchar_t *)&(map_entry->map_pwwn)); if (ptgt == NULL) { continue; } mutex_enter(&ptgt->tgt_mutex); ptgt->tgt_trace = 0; ptgt->tgt_change_cnt++; ptgt->tgt_statec_cause = cause; ptgt->tgt_tmp_cnt = 1; fcp_update_tgt_state(ptgt, FCP_SET, state); mutex_exit(&ptgt->tgt_mutex); } } static int fcp_call_finish_init(struct fcp_port *pptr, struct fcp_tgt *ptgt, int lcount, int tcount, int cause) { int rval; mutex_enter(&pptr->port_mutex); rval = fcp_call_finish_init_held(pptr, ptgt, lcount, tcount, cause); mutex_exit(&pptr->port_mutex); return (rval); } static int fcp_call_finish_init_held(struct fcp_port *pptr, struct fcp_tgt *ptgt, int lcount, int tcount, int cause) { int finish_init = 0; int finish_tgt = 0; int do_finish_init = 0; int rval = FCP_NO_CHANGE; if (cause == FCP_CAUSE_LINK_CHANGE || cause == FCP_CAUSE_LINK_DOWN) { do_finish_init = 1; } if (ptgt != NULL) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "link_cnt: %d,%d; tgt_cnt: %d,%d; tmp_cnt: %d,%d;" " cause = %d, d_id = 0x%x, tgt_done = %d", pptr->port_link_cnt, lcount, ptgt->tgt_change_cnt, tcount, pptr->port_tmp_cnt, ptgt->tgt_tmp_cnt, cause, ptgt->tgt_d_id, ptgt->tgt_done); mutex_enter(&ptgt->tgt_mutex); if (tcount && (ptgt->tgt_change_cnt != tcount)) { rval = FCP_DEV_CHANGE; if (do_finish_init && ptgt->tgt_done == 0) { ptgt->tgt_done++; finish_init = 1; } } else { if (--ptgt->tgt_tmp_cnt <= 0) { ptgt->tgt_tmp_cnt = 0; finish_tgt = 1; if (do_finish_init) { finish_init = 1; } } } mutex_exit(&ptgt->tgt_mutex); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "Call Finish Init for NO target"); if (do_finish_init) { finish_init = 1; } } if (finish_tgt) { ASSERT(ptgt != NULL); mutex_enter(&ptgt->tgt_mutex); #ifdef DEBUG bzero(ptgt->tgt_tmp_cnt_stack, sizeof (ptgt->tgt_tmp_cnt_stack)); ptgt->tgt_tmp_cnt_depth = getpcstack(ptgt->tgt_tmp_cnt_stack, FCP_STACK_DEPTH); #endif /* DEBUG */ mutex_exit(&ptgt->tgt_mutex); (void) fcp_finish_tgt(pptr, ptgt, lcount, tcount, cause); } if (finish_init && lcount == pptr->port_link_cnt) { ASSERT(pptr->port_tmp_cnt > 0); if (--pptr->port_tmp_cnt == 0) { fcp_finish_init(pptr); } } else if (lcount != pptr->port_link_cnt) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_call_finish_init_held,1: state change occured" " for D_ID=0x%x", (ptgt) ? ptgt->tgt_d_id : 0); } return (rval); } static void fcp_reconfigure_luns(void * tgt_handle) { uint32_t dev_cnt; fc_portmap_t *devlist; struct fcp_tgt *ptgt = (struct fcp_tgt *)tgt_handle; struct fcp_port *pptr = ptgt->tgt_port; /* * If the timer that fires this off got canceled too late, the * target could have been destroyed. */ if (ptgt->tgt_tid == NULL) { return; } devlist = kmem_zalloc(sizeof (*devlist), KM_NOSLEEP); if (devlist == NULL) { fcp_log(CE_WARN, pptr->port_dip, "!fcp%d: failed to allocate for portmap", pptr->port_instance); return; } dev_cnt = 1; devlist->map_pd = ptgt->tgt_pd_handle; devlist->map_hard_addr.hard_addr = ptgt->tgt_hard_addr; devlist->map_did.port_id = ptgt->tgt_d_id; bcopy(&ptgt->tgt_node_wwn.raw_wwn[0], &devlist->map_nwwn, FC_WWN_SIZE); bcopy(&ptgt->tgt_port_wwn.raw_wwn[0], &devlist->map_pwwn, FC_WWN_SIZE); devlist->map_state = PORT_DEVICE_LOGGED_IN; devlist->map_type = PORT_DEVICE_NEW; devlist->map_flags = 0; fcp_statec_callback(NULL, pptr->port_fp_handle, FC_STATE_DEVICE_CHANGE, pptr->port_topology, devlist, dev_cnt, pptr->port_id); /* * Clear the tgt_tid after no more references to * the fcp_tgt */ mutex_enter(&ptgt->tgt_mutex); ptgt->tgt_tid = NULL; mutex_exit(&ptgt->tgt_mutex); kmem_free(devlist, sizeof (*devlist)); } static void fcp_free_targets(struct fcp_port *pptr) { int i; struct fcp_tgt *ptgt; mutex_enter(&pptr->port_mutex); for (i = 0; i < FCP_NUM_HASH; i++) { ptgt = pptr->port_tgt_hash_table[i]; while (ptgt != NULL) { struct fcp_tgt *next_tgt = ptgt->tgt_next; fcp_free_target(ptgt); ptgt = next_tgt; } } mutex_exit(&pptr->port_mutex); } static void fcp_free_target(struct fcp_tgt *ptgt) { struct fcp_lun *plun; timeout_id_t tid; mutex_enter(&ptgt->tgt_mutex); tid = ptgt->tgt_tid; /* * Cancel any pending timeouts for this target. */ if (tid != NULL) { /* * Set tgt_tid to NULL first to avoid a race in the callback. * If tgt_tid is NULL, the callback will simply return. */ ptgt->tgt_tid = NULL; mutex_exit(&ptgt->tgt_mutex); (void) untimeout(tid); mutex_enter(&ptgt->tgt_mutex); } plun = ptgt->tgt_lun; while (plun != NULL) { struct fcp_lun *next_lun = plun->lun_next; fcp_dealloc_lun(plun); plun = next_lun; } mutex_exit(&ptgt->tgt_mutex); fcp_dealloc_tgt(ptgt); } /* * Function: fcp_is_retryable * * Description: Indicates if the internal packet is retryable. * * Argument: *icmd FCP internal packet. * * Return Value: 0 Not retryable * 1 Retryable * * Context: User, Kernel and Interrupt context */ static int fcp_is_retryable(struct fcp_ipkt *icmd) { if (icmd->ipkt_port->port_state & (FCP_STATE_SUSPENDED | FCP_STATE_DETACHING | FCP_STATE_POWER_DOWN)) { return (0); } return (((fcp_watchdog_time + icmd->ipkt_fpkt->pkt_timeout) < icmd->ipkt_port->port_deadline) ? 1 : 0); } /* * Function: fcp_create_on_demand * * Argument: *pptr FCP port. * *pwwn Port WWN. * * Return Value: 0 Success * EIO * ENOMEM * EBUSY * EINVAL * * Context: User and Kernel context */ static int fcp_create_on_demand(struct fcp_port *pptr, uchar_t *pwwn) { int wait_ms; int tcount; int lcount; int ret; int error; int rval = EIO; int ntries; fc_portmap_t *devlist; opaque_t pd; struct fcp_lun *plun; struct fcp_tgt *ptgt; int old_manual = 0; /* Allocates the fc_portmap_t structure. */ devlist = kmem_zalloc(sizeof (*devlist), KM_SLEEP); /* * If FC_INVALID_RSCN_COUNT is non-zero, we will have to init as shown * in the commented statement below: * * devlist->map_rscn_info.ulp_rscn_count = FC_INVALID_RSCN_COUNT; * * Below, the deadline for the discovery process is set. */ mutex_enter(&pptr->port_mutex); pptr->port_deadline = fcp_watchdog_time + FCP_ICMD_DEADLINE; mutex_exit(&pptr->port_mutex); /* * We try to find the remote port based on the WWN provided by the * caller. We actually ask fp/fctl if it has it. */ pd = fc_ulp_get_remote_port(pptr->port_fp_handle, (la_wwn_t *)pwwn, &error, 1); if (pd == NULL) { kmem_free(devlist, sizeof (*devlist)); return (rval); } /* * The remote port was found. We ask fp/fctl to update our * fc_portmap_t structure. */ ret = fc_ulp_pwwn_to_portmap(pptr->port_fp_handle, (la_wwn_t *)pwwn, devlist); if (ret != FC_SUCCESS) { kmem_free(devlist, sizeof (*devlist)); return (rval); } /* * The map flag field is set to indicates that the creation is being * done at the user request (Ioclt probably luxadm or cfgadm). */ devlist->map_type = PORT_DEVICE_USER_CREATE; mutex_enter(&pptr->port_mutex); /* * We check to see if fcp already has a target that describes the * device being created. If not it is created. */ ptgt = fcp_lookup_target(pptr, pwwn); if (ptgt == NULL) { lcount = pptr->port_link_cnt; mutex_exit(&pptr->port_mutex); ptgt = fcp_alloc_tgt(pptr, devlist, lcount); if (ptgt == NULL) { fcp_log(CE_WARN, pptr->port_dip, "!FC target allocation failed"); return (ENOMEM); } mutex_enter(&pptr->port_mutex); } mutex_enter(&ptgt->tgt_mutex); ptgt->tgt_statec_cause = FCP_CAUSE_USER_CREATE; ptgt->tgt_tmp_cnt = 1; ptgt->tgt_device_created = 0; /* * If fabric and auto config is set but the target was * manually unconfigured then reset to the manual_config_only to * 0 so the device will get configured. */ if (FC_TOP_EXTERNAL(pptr->port_topology) && fcp_enable_auto_configuration && ptgt->tgt_manual_config_only == 1) { old_manual = 1; ptgt->tgt_manual_config_only = 0; } mutex_exit(&ptgt->tgt_mutex); fcp_update_targets(pptr, devlist, 1, FCP_LUN_BUSY | FCP_LUN_MARK, FCP_CAUSE_USER_CREATE); lcount = pptr->port_link_cnt; tcount = ptgt->tgt_change_cnt; if (fcp_handle_mapflags(pptr, ptgt, devlist, lcount, tcount, FCP_CAUSE_USER_CREATE) == TRUE) { if (FC_TOP_EXTERNAL(pptr->port_topology) && fcp_enable_auto_configuration && old_manual) { mutex_enter(&ptgt->tgt_mutex); ptgt->tgt_manual_config_only = 1; mutex_exit(&ptgt->tgt_mutex); } if (pptr->port_link_cnt != lcount || ptgt->tgt_change_cnt != tcount) { rval = EBUSY; } mutex_exit(&pptr->port_mutex); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_create_on_demand: mapflags ptgt=%x, " "lcount=%x::port_link_cnt=%x, " "tcount=%x: tgt_change_cnt=%x, rval=%x", ptgt, lcount, pptr->port_link_cnt, tcount, ptgt->tgt_change_cnt, rval); return (rval); } /* * Due to lack of synchronization mechanisms, we perform * periodic monitoring of our request; Because requests * get dropped when another one supercedes (either because * of a link change or a target change), it is difficult to * provide a clean synchronization mechanism (such as a * semaphore or a conditional variable) without exhaustively * rewriting the mainline discovery code of this driver. */ wait_ms = 500; ntries = fcp_max_target_retries; FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_create_on_demand(1): ntries=%x, ptgt=%x, " "lcount=%x::port_link_cnt=%x, " "tcount=%x::tgt_change_cnt=%x, rval=%x, tgt_device_created=%x " "tgt_tmp_cnt =%x", ntries, ptgt, lcount, pptr->port_link_cnt, tcount, ptgt->tgt_change_cnt, rval, ptgt->tgt_device_created, ptgt->tgt_tmp_cnt); mutex_enter(&ptgt->tgt_mutex); while (ntries-- != 0 && pptr->port_link_cnt == lcount && ptgt->tgt_change_cnt == tcount && ptgt->tgt_device_created == 0) { mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); delay(drv_usectohz(wait_ms * 1000)); mutex_enter(&pptr->port_mutex); mutex_enter(&ptgt->tgt_mutex); } if (pptr->port_link_cnt != lcount || ptgt->tgt_change_cnt != tcount) { rval = EBUSY; } else { if (ptgt->tgt_tmp_cnt == 0 && ptgt->tgt_node_state == FCP_TGT_NODE_PRESENT) { rval = 0; } } FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_create_on_demand(2): ntries=%x, ptgt=%x, " "lcount=%x::port_link_cnt=%x, " "tcount=%x::tgt_change_cnt=%x, rval=%x, tgt_device_created=%x " "tgt_tmp_cnt =%x", ntries, ptgt, lcount, pptr->port_link_cnt, tcount, ptgt->tgt_change_cnt, rval, ptgt->tgt_device_created, ptgt->tgt_tmp_cnt); if (rval) { if (FC_TOP_EXTERNAL(pptr->port_topology) && fcp_enable_auto_configuration && old_manual) { ptgt->tgt_manual_config_only = 1; } mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); kmem_free(devlist, sizeof (*devlist)); FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_create_on_demand(3): ntries=%x, ptgt=%x, " "lcount=%x::port_link_cnt=%x, " "tcount=%x::tgt_change_cnt=%x, rval=%x, " "tgt_device_created=%x, tgt D_ID=%x", ntries, ptgt, lcount, pptr->port_link_cnt, tcount, ptgt->tgt_change_cnt, rval, ptgt->tgt_device_created, ptgt->tgt_d_id); return (rval); } if ((plun = ptgt->tgt_lun) != NULL) { tcount = plun->lun_tgt->tgt_change_cnt; } else { rval = EINVAL; } lcount = pptr->port_link_cnt; /* * Configuring the target with no LUNs will fail. We * should reset the node state so that it is not * automatically configured when the LUNs are added * to this target. */ if (ptgt->tgt_lun_cnt == 0) { ptgt->tgt_node_state = FCP_TGT_NODE_NONE; } mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); while (plun) { child_info_t *cip; mutex_enter(&plun->lun_mutex); cip = plun->lun_cip; mutex_exit(&plun->lun_mutex); mutex_enter(&ptgt->tgt_mutex); if (!(plun->lun_state & FCP_LUN_OFFLINE)) { mutex_exit(&ptgt->tgt_mutex); rval = fcp_pass_to_hp_and_wait(pptr, plun, cip, FCP_ONLINE, lcount, tcount, NDI_ONLINE_ATTACH); if (rval != NDI_SUCCESS) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_create_on_demand: " "pass_to_hp_and_wait failed " "rval=%x", rval); rval = EIO; } else { mutex_enter(&LUN_TGT->tgt_mutex); plun->lun_state &= ~(FCP_LUN_OFFLINE | FCP_LUN_BUSY); mutex_exit(&LUN_TGT->tgt_mutex); } mutex_enter(&ptgt->tgt_mutex); } plun = plun->lun_next; mutex_exit(&ptgt->tgt_mutex); } kmem_free(devlist, sizeof (*devlist)); if (FC_TOP_EXTERNAL(pptr->port_topology) && fcp_enable_auto_configuration && old_manual) { mutex_enter(&ptgt->tgt_mutex); /* if successful then set manual to 0 */ if (rval == 0) { ptgt->tgt_manual_config_only = 0; } else { /* reset to 1 so the user has to do the config */ ptgt->tgt_manual_config_only = 1; } mutex_exit(&ptgt->tgt_mutex); } return (rval); } static void fcp_ascii_to_wwn(caddr_t string, uchar_t bytes[], unsigned int byte_len) { int count; uchar_t byte; count = 0; while (*string) { byte = FCP_ATOB(*string); string++; byte = byte << 4 | FCP_ATOB(*string); string++; bytes[count++] = byte; if (count >= byte_len) { break; } } } static void fcp_wwn_to_ascii(uchar_t wwn[], char *string) { int i; for (i = 0; i < FC_WWN_SIZE; i++) { (void) sprintf(string + (i * 2), "%02x", wwn[i]); } } static void fcp_print_error(fc_packet_t *fpkt) { struct fcp_ipkt *icmd = (struct fcp_ipkt *) fpkt->pkt_ulp_private; struct fcp_port *pptr; struct fcp_tgt *ptgt; struct fcp_lun *plun; caddr_t buf; int scsi_cmd = 0; ptgt = icmd->ipkt_tgt; plun = icmd->ipkt_lun; pptr = ptgt->tgt_port; buf = kmem_zalloc(256, KM_NOSLEEP); if (buf == NULL) { return; } switch (icmd->ipkt_opcode) { case SCMD_REPORT_LUN: (void) sprintf(buf, "!REPORT LUN to D_ID=0x%%x" " lun=0x%%x failed"); scsi_cmd++; break; case SCMD_INQUIRY_PAGE83: (void) sprintf(buf, "!INQUIRY-83 to D_ID=0x%%x" " lun=0x%%x failed"); scsi_cmd++; break; case SCMD_INQUIRY: (void) sprintf(buf, "!INQUIRY to D_ID=0x%%x" " lun=0x%%x failed"); scsi_cmd++; break; case LA_ELS_PLOGI: (void) sprintf(buf, "!PLOGI to D_ID=0x%%x failed"); break; case LA_ELS_PRLI: (void) sprintf(buf, "!PRLI to D_ID=0x%%x failed"); break; } if (scsi_cmd && fpkt->pkt_state == FC_PKT_SUCCESS) { struct fcp_rsp response, *rsp; uchar_t asc, ascq; caddr_t sense_key = NULL; struct fcp_rsp_info fcp_rsp_err, *bep; if (icmd->ipkt_nodma) { rsp = (struct fcp_rsp *)fpkt->pkt_resp; bep = (struct fcp_rsp_info *)((caddr_t)rsp + sizeof (struct fcp_rsp)); } else { rsp = &response; bep = &fcp_rsp_err; FCP_CP_IN(fpkt->pkt_resp, rsp, fpkt->pkt_resp_acc, sizeof (struct fcp_rsp)); FCP_CP_IN(fpkt->pkt_resp + sizeof (struct fcp_rsp), bep, fpkt->pkt_resp_acc, sizeof (struct fcp_rsp_info)); } if (fcp_validate_fcp_response(rsp, pptr) != FC_SUCCESS) { (void) sprintf(buf + strlen(buf), " : Bad FCP response values rsvd1=%%x, rsvd2=%%x," " sts-rsvd1=%%x, sts-rsvd2=%%x, rsplen=%%x," " senselen=%%x. Giving up"); fcp_log(CE_WARN, pptr->port_dip, buf, ptgt->tgt_d_id, plun->lun_num, rsp->reserved_0, rsp->reserved_1, rsp->fcp_u.fcp_status.reserved_0, rsp->fcp_u.fcp_status.reserved_1, rsp->fcp_response_len, rsp->fcp_sense_len); kmem_free(buf, 256); return; } if (rsp->fcp_u.fcp_status.rsp_len_set && bep->rsp_code != FCP_NO_FAILURE) { (void) sprintf(buf + strlen(buf), " FCP Response code = 0x%x", bep->rsp_code); } if (rsp->fcp_u.fcp_status.scsi_status & STATUS_CHECK) { struct scsi_extended_sense sense_info, *sense_ptr; if (icmd->ipkt_nodma) { sense_ptr = (struct scsi_extended_sense *) ((caddr_t)fpkt->pkt_resp + sizeof (struct fcp_rsp) + rsp->fcp_response_len); } else { sense_ptr = &sense_info; FCP_CP_IN(fpkt->pkt_resp + sizeof (struct fcp_rsp) + rsp->fcp_response_len, &sense_info, fpkt->pkt_resp_acc, sizeof (struct scsi_extended_sense)); } if (sense_ptr->es_key < NUM_SENSE_KEYS + NUM_IMPL_SENSE_KEYS) { sense_key = sense_keys[sense_ptr->es_key]; } else { sense_key = "Undefined"; } asc = sense_ptr->es_add_code; ascq = sense_ptr->es_qual_code; (void) sprintf(buf + strlen(buf), ": sense key=%%s, ASC=%%x," " ASCQ=%%x." " Giving up"); fcp_log(CE_WARN, pptr->port_dip, buf, ptgt->tgt_d_id, plun->lun_num, sense_key, asc, ascq); } else { (void) sprintf(buf + strlen(buf), " : SCSI status=%%x. Giving up"); fcp_log(CE_WARN, pptr->port_dip, buf, ptgt->tgt_d_id, plun->lun_num, rsp->fcp_u.fcp_status.scsi_status); } } else { caddr_t state, reason, action, expln; (void) fc_ulp_pkt_error(fpkt, &state, &reason, &action, &expln); (void) sprintf(buf + strlen(buf), ": State:%%s," " Reason:%%s. Giving up"); if (scsi_cmd) { fcp_log(CE_WARN, pptr->port_dip, buf, ptgt->tgt_d_id, plun->lun_num, state, reason); } else { fcp_log(CE_WARN, pptr->port_dip, buf, ptgt->tgt_d_id, state, reason); } } kmem_free(buf, 256); } static int fcp_handle_ipkt_errors(struct fcp_port *pptr, struct fcp_tgt *ptgt, struct fcp_ipkt *icmd, int rval, caddr_t op) { int ret = DDI_FAILURE; char *error; switch (rval) { case FC_DEVICE_BUSY_NEW_RSCN: /* * This means that there was a new RSCN that the transport * knows about (which the ULP *may* know about too) but the * pkt that was sent down was related to an older RSCN. So, we * are just going to reset the retry count and deadline and * continue to retry. The idea is that transport is currently * working on the new RSCN and will soon let the ULPs know * about it and when it does the existing logic will kick in * where it will change the tcount to indicate that something * changed on the target. So, rediscovery will start and there * will not be an infinite retry. * * For a full flow of how the RSCN info is transferred back and * forth, see fp.c */ icmd->ipkt_retries = 0; icmd->ipkt_port->port_deadline = fcp_watchdog_time + FCP_ICMD_DEADLINE; FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_handle_ipkt_errors: rval=%x for D_ID=%x", rval, ptgt->tgt_d_id); /* FALLTHROUGH */ case FC_STATEC_BUSY: case FC_DEVICE_BUSY: case FC_PBUSY: case FC_FBUSY: case FC_TRAN_BUSY: case FC_OFFLINE: FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_handle_ipkt_errors: rval=%x for D_ID=%x", rval, ptgt->tgt_d_id); if (icmd->ipkt_retries < FCP_MAX_RETRIES && fcp_is_retryable(icmd)) { fcp_queue_ipkt(pptr, icmd->ipkt_fpkt); ret = DDI_SUCCESS; } break; case FC_LOGINREQ: /* * FC_LOGINREQ used to be handled just like all the cases * above. It has been changed to handled a PRLI that fails * with FC_LOGINREQ different than other ipkts that fail * with FC_LOGINREQ. If a PRLI fails with FC_LOGINREQ it is * a simple matter to turn it into a PLOGI instead, so that's * exactly what we do here. */ if (icmd->ipkt_opcode == LA_ELS_PRLI) { ret = fcp_send_els(icmd->ipkt_port, icmd->ipkt_tgt, icmd, LA_ELS_PLOGI, icmd->ipkt_link_cnt, icmd->ipkt_change_cnt, icmd->ipkt_cause); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_3, 0, "fcp_handle_ipkt_errors: rval=%x for D_ID=%x", rval, ptgt->tgt_d_id); if (icmd->ipkt_retries < FCP_MAX_RETRIES && fcp_is_retryable(icmd)) { fcp_queue_ipkt(pptr, icmd->ipkt_fpkt); ret = DDI_SUCCESS; } } break; default: mutex_enter(&pptr->port_mutex); mutex_enter(&ptgt->tgt_mutex); if (!FCP_STATE_CHANGED(pptr, ptgt, icmd)) { mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); (void) fc_ulp_error(rval, &error); fcp_log(CE_WARN, pptr->port_dip, "!Failed to send %s to D_ID=%x error=%s", op, ptgt->tgt_d_id, error); } else { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "fcp_handle_ipkt_errors,1: state change occured" " for D_ID=0x%x", ptgt->tgt_d_id); mutex_exit(&ptgt->tgt_mutex); mutex_exit(&pptr->port_mutex); } break; } return (ret); } /* * Check of outstanding commands on any LUN for this target */ static int fcp_outstanding_lun_cmds(struct fcp_tgt *ptgt) { struct fcp_lun *plun; struct fcp_pkt *cmd; for (plun = ptgt->tgt_lun; plun != NULL; plun = plun->lun_next) { mutex_enter(&plun->lun_mutex); for (cmd = plun->lun_pkt_head; cmd != NULL; cmd = cmd->cmd_forw) { if (cmd->cmd_state == FCP_PKT_ISSUED) { mutex_exit(&plun->lun_mutex); return (FC_SUCCESS); } } mutex_exit(&plun->lun_mutex); } return (FC_FAILURE); } static fc_portmap_t * fcp_construct_map(struct fcp_port *pptr, uint32_t *dev_cnt) { int i; fc_portmap_t *devlist; fc_portmap_t *devptr = NULL; struct fcp_tgt *ptgt; mutex_enter(&pptr->port_mutex); for (i = 0, *dev_cnt = 0; i < FCP_NUM_HASH; i++) { for (ptgt = pptr->port_tgt_hash_table[i]; ptgt != NULL; ptgt = ptgt->tgt_next) { if (!(ptgt->tgt_state & FCP_TGT_ORPHAN)) { ++*dev_cnt; } } } devptr = devlist = kmem_zalloc(sizeof (*devlist) * *dev_cnt, KM_NOSLEEP); if (devlist == NULL) { mutex_exit(&pptr->port_mutex); fcp_log(CE_WARN, pptr->port_dip, "!fcp%d: failed to allocate for portmap for construct map", pptr->port_instance); return (devptr); } for (i = 0; i < FCP_NUM_HASH; i++) { for (ptgt = pptr->port_tgt_hash_table[i]; ptgt != NULL; ptgt = ptgt->tgt_next) { if (!(ptgt->tgt_state & FCP_TGT_ORPHAN)) { int ret; ret = fc_ulp_pwwn_to_portmap( pptr->port_fp_handle, (la_wwn_t *)&ptgt->tgt_port_wwn.raw_wwn[0], devlist); if (ret == FC_SUCCESS) { devlist++; continue; } devlist->map_pd = NULL; devlist->map_did.port_id = ptgt->tgt_d_id; devlist->map_hard_addr.hard_addr = ptgt->tgt_hard_addr; devlist->map_state = PORT_DEVICE_INVALID; devlist->map_type = PORT_DEVICE_OLD; bcopy(&ptgt->tgt_node_wwn.raw_wwn[0], &devlist->map_nwwn, FC_WWN_SIZE); bcopy(&ptgt->tgt_port_wwn.raw_wwn[0], &devlist->map_pwwn, FC_WWN_SIZE); devlist++; } } } mutex_exit(&pptr->port_mutex); return (devptr); } /* * Inimate MPxIO that the lun is busy and cannot accept regular IO */ static void fcp_update_mpxio_path_verifybusy(struct fcp_port *pptr) { int i; struct fcp_tgt *ptgt; struct fcp_lun *plun; for (i = 0; i < FCP_NUM_HASH; i++) { for (ptgt = pptr->port_tgt_hash_table[i]; ptgt != NULL; ptgt = ptgt->tgt_next) { mutex_enter(&ptgt->tgt_mutex); for (plun = ptgt->tgt_lun; plun != NULL; plun = plun->lun_next) { if (plun->lun_mpxio && plun->lun_state & FCP_LUN_BUSY) { if (!fcp_pass_to_hp(pptr, plun, plun->lun_cip, FCP_MPXIO_PATH_SET_BUSY, pptr->port_link_cnt, ptgt->tgt_change_cnt, 0, 0)) { FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace, FCP_BUF_LEVEL_2, 0, "path_verifybusy: " "disable lun %p failed!", plun); } } } mutex_exit(&ptgt->tgt_mutex); } } } static int fcp_update_mpxio_path(struct fcp_lun *plun, child_info_t *cip, int what) { dev_info_t *cdip = NULL; dev_info_t *pdip = NULL; ASSERT(plun); mutex_enter(&plun->lun_mutex); if (fcp_is_child_present(plun, cip) == FC_FAILURE) { mutex_exit(&plun->lun_mutex); return (NDI_FAILURE); } mutex_exit(&plun->lun_mutex); cdip = mdi_pi_get_client(PIP(cip)); pdip = mdi_pi_get_phci(PIP(cip)); ASSERT(cdip != NULL); ASSERT(pdip != NULL); if (what == FCP_MPXIO_PATH_CLEAR_BUSY) { /* LUN ready for IO */ (void) mdi_pi_enable_path(PIP(cip), DRIVER_DISABLE_TRANSIENT); } else { /* LUN busy to accept IO */ (void) mdi_pi_disable_path(PIP(cip), DRIVER_DISABLE_TRANSIENT); } return (NDI_SUCCESS); } /* * Caller must free the returned string of MAXPATHLEN len * If the device is offline (-1 instance number) NULL * will be returned. */ static char * fcp_get_lun_path(struct fcp_lun *plun) { dev_info_t *dip = NULL; char *path = NULL; if (plun == NULL) { return (NULL); } if (plun->lun_mpxio == 0) { dip = DIP(plun->lun_cip); } else { dip = mdi_pi_get_client(PIP(plun->lun_cip)); } if (dip == NULL) { return (NULL); } if (ddi_get_instance(dip) < 0) { return (NULL); } path = kmem_alloc(MAXPATHLEN, KM_SLEEP); if (path == NULL) { return (NULL); } (void) ddi_pathname(dip, path); /* * In reality, the user wants a fully valid path (one they can open) * but this string is lacking the mount point, and the minor node. * It would be nice if we could "figure these out" somehow * and fill them in. Otherwise, the userland code has to understand * driver specific details of which minor node is the "best" or * "right" one to expose. (Ex: which slice is the whole disk, or * which tape doesn't rewind) */ return (path); } static int fcp_scsi_bus_config(dev_info_t *parent, uint_t flag, ddi_bus_config_op_t op, void *arg, dev_info_t **childp) { int64_t reset_delay; int rval, retry = 0; struct fcp_port *pptr = fcp_dip2port(parent); reset_delay = (int64_t)(USEC_TO_TICK(FCP_INIT_WAIT_TIMEOUT)) - (lbolt64 - pptr->port_attach_time); if (reset_delay < 0) { reset_delay = 0; } if (fcp_bus_config_debug) { flag |= NDI_DEVI_DEBUG; } switch (op) { case BUS_CONFIG_ONE: /* * Retry the command since we need to ensure * the fabric devices are available for root */ while (retry++ < fcp_max_bus_config_retries) { rval = (ndi_busop_bus_config(parent, flag | NDI_MDI_FALLBACK, op, arg, childp, (clock_t)reset_delay)); if (rval == 0) { return (rval); } } /* * drain taskq to make sure nodes are created and then * try again. */ taskq_wait(DEVI(parent)->devi_taskq); return (ndi_busop_bus_config(parent, flag | NDI_MDI_FALLBACK, op, arg, childp, 0)); case BUS_CONFIG_DRIVER: case BUS_CONFIG_ALL: { /* * delay till all devices report in (port_tmp_cnt == 0) * or FCP_INIT_WAIT_TIMEOUT */ mutex_enter(&pptr->port_mutex); while ((reset_delay > 0) && pptr->port_tmp_cnt) { (void) cv_timedwait(&pptr->port_config_cv, &pptr->port_mutex, ddi_get_lbolt() + (clock_t)reset_delay); reset_delay = (int64_t)(USEC_TO_TICK(FCP_INIT_WAIT_TIMEOUT)) - (lbolt64 - pptr->port_attach_time); } mutex_exit(&pptr->port_mutex); /* drain taskq to make sure nodes are created */ taskq_wait(DEVI(parent)->devi_taskq); return (ndi_busop_bus_config(parent, flag, op, arg, childp, 0)); } default: return (NDI_FAILURE); } /*NOTREACHED*/ } static int fcp_scsi_bus_unconfig(dev_info_t *parent, uint_t flag, ddi_bus_config_op_t op, void *arg) { if (fcp_bus_config_debug) { flag |= NDI_DEVI_DEBUG; } return (ndi_busop_bus_unconfig(parent, flag, op, arg)); } /* * Routine to copy GUID into the lun structure. * returns 0 if copy was successful and 1 if encountered a * failure and did not copy the guid. */ static int fcp_copy_guid_2_lun_block(struct fcp_lun *plun, char *guidp) { int retval = 0; /* add one for the null terminator */ const unsigned int len = strlen(guidp) + 1; if ((guidp == NULL) || (plun == NULL)) { return (1); } /* * if the plun->lun_guid already has been allocated, * then check the size. if the size is exact, reuse * it....if not free it an allocate the required size. * The reallocation should NOT typically happen * unless the GUIDs reported changes between passes. * We free up and alloc again even if the * size was more than required. This is due to the * fact that the field lun_guid_size - serves * dual role of indicating the size of the wwn * size and ALSO the allocation size. */ if (plun->lun_guid) { if (plun->lun_guid_size != len) { /* * free the allocated memory and * initialize the field * lun_guid_size to 0. */ kmem_free(plun->lun_guid, plun->lun_guid_size); plun->lun_guid = NULL; plun->lun_guid_size = 0; } } /* * alloc only if not already done. */ if (plun->lun_guid == NULL) { plun->lun_guid = kmem_zalloc(len, KM_NOSLEEP); if (plun->lun_guid == NULL) { cmn_err(CE_WARN, "fcp_copy_guid_2_lun_block:" "Unable to allocate" "Memory for GUID!!! size %d", len); retval = 1; } else { plun->lun_guid_size = len; } } if (plun->lun_guid) { /* * now copy the GUID */ bcopy(guidp, plun->lun_guid, plun->lun_guid_size); } return (retval); } /* * fcp_reconfig_wait * * Wait for a rediscovery/reconfiguration to complete before continuing. */ static void fcp_reconfig_wait(struct fcp_port *pptr) { clock_t reconfig_start, wait_timeout; /* * Quick check. If pptr->port_tmp_cnt is 0, there is no * reconfiguration in progress. */ mutex_enter(&pptr->port_mutex); if (pptr->port_tmp_cnt == 0) { mutex_exit(&pptr->port_mutex); return; } mutex_exit(&pptr->port_mutex); /* * If we cause a reconfig by raising power, delay until all devices * report in (port_tmp_cnt returns to 0) */ reconfig_start = ddi_get_lbolt(); wait_timeout = drv_usectohz(FCP_INIT_WAIT_TIMEOUT); mutex_enter(&pptr->port_mutex); while (((ddi_get_lbolt() - reconfig_start) < wait_timeout) && pptr->port_tmp_cnt) { (void) cv_timedwait(&pptr->port_config_cv, &pptr->port_mutex, reconfig_start + wait_timeout); } mutex_exit(&pptr->port_mutex); /* * Even if fcp_tmp_count isn't 0, continue without error. The port * we want may still be ok. If not, it will error out later */ } /* * Read masking info from fp.conf and construct the global fcp_lun_blacklist. * We rely on the fcp_global_mutex to provide protection against changes to * the fcp_lun_blacklist. * * You can describe a list of target port WWNs and LUN numbers which will * not be configured. LUN numbers will be interpreted as decimal. White * spaces and ',' can be used in the list of LUN numbers. * * To prevent LUNs 1 and 2 from being configured for target * port 510000f010fd92a1 and target port 510000e012079df1, set: * * pwwn-lun-blacklist= * "510000f010fd92a1,1,2", * "510000e012079df1,1,2"; */ static void fcp_read_blacklist(dev_info_t *dip, struct fcp_black_list_entry **pplun_blacklist) { char **prop_array = NULL; char *curr_pwwn = NULL; char *curr_lun = NULL; uint32_t prop_item = 0; int idx = 0; int len = 0; ASSERT(mutex_owned(&fcp_global_mutex)); if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, LUN_BLACKLIST_PROP, &prop_array, &prop_item) != DDI_PROP_SUCCESS) { return; } for (idx = 0; idx < prop_item; idx++) { curr_pwwn = prop_array[idx]; while (*curr_pwwn == ' ') { curr_pwwn++; } if (strlen(curr_pwwn) <= (sizeof (la_wwn_t) * 2 + 1)) { fcp_log(CE_WARN, NULL, "Invalid WWN %s in the blacklist" ", please check.", curr_pwwn); continue; } if ((*(curr_pwwn + sizeof (la_wwn_t) * 2) != ' ') && (*(curr_pwwn + sizeof (la_wwn_t) * 2) != ',')) { fcp_log(CE_WARN, NULL, "Invalid WWN %s in the blacklist" ", please check.", curr_pwwn); continue; } for (len = 0; len < sizeof (la_wwn_t) * 2; len++) { if (isxdigit(curr_pwwn[len]) != TRUE) { fcp_log(CE_WARN, NULL, "Invalid WWN %s in the " "blacklist, please check.", curr_pwwn); break; } } if (len != sizeof (la_wwn_t) * 2) { continue; } curr_lun = curr_pwwn + sizeof (la_wwn_t) * 2 + 1; *(curr_lun - 1) = '\0'; fcp_mask_pwwn_lun(curr_pwwn, curr_lun, pplun_blacklist); } ddi_prop_free(prop_array); } /* * Get the masking info about one remote target port designated by wwn. * Lun ids could be separated by ',' or white spaces. */ static void fcp_mask_pwwn_lun(char *curr_pwwn, char *curr_lun, struct fcp_black_list_entry **pplun_blacklist) { int idx = 0; uint32_t offset = 0; unsigned long lun_id = 0; char lunid_buf[16]; char *pend = NULL; int illegal_digit = 0; while (offset < strlen(curr_lun)) { while ((curr_lun[offset + idx] != ',') && (curr_lun[offset + idx] != '\0') && (curr_lun[offset + idx] != ' ')) { if (isdigit(curr_lun[offset + idx]) == 0) { illegal_digit++; } idx++; } if (illegal_digit > 0) { offset += (idx+1); /* To the start of next lun */ idx = 0; illegal_digit = 0; fcp_log(CE_WARN, NULL, "Invalid LUN %s for WWN %s in " "the blacklist, please check digits.", curr_lun, curr_pwwn); continue; } if (idx >= (sizeof (lunid_buf) / sizeof (lunid_buf[0]))) { fcp_log(CE_WARN, NULL, "Invalid LUN %s for WWN %s in " "the blacklist, please check the length of LUN#.", curr_lun, curr_pwwn); break; } if (idx == 0) { /* ignore ' ' or ',' or '\0' */ offset++; continue; } bcopy(curr_lun + offset, lunid_buf, idx); lunid_buf[idx] = '\0'; if (ddi_strtoul(lunid_buf, &pend, 10, &lun_id) == 0) { fcp_add_one_mask(curr_pwwn, lun_id, pplun_blacklist); } else { fcp_log(CE_WARN, NULL, "Invalid LUN %s for WWN %s in " "the blacklist, please check %s.", curr_lun, curr_pwwn, lunid_buf); } offset += (idx+1); /* To the start of next lun */ idx = 0; } } /* * Add one masking record */ static void fcp_add_one_mask(char *curr_pwwn, uint32_t lun_id, struct fcp_black_list_entry **pplun_blacklist) { struct fcp_black_list_entry *tmp_entry = *pplun_blacklist; struct fcp_black_list_entry *new_entry = NULL; la_wwn_t wwn; fcp_ascii_to_wwn(curr_pwwn, wwn.raw_wwn, sizeof (la_wwn_t)); while (tmp_entry) { if ((bcmp(&tmp_entry->wwn, &wwn, sizeof (la_wwn_t)) == 0) && (tmp_entry->lun == lun_id)) { return; } tmp_entry = tmp_entry->next; } /* add to black list */ new_entry = (struct fcp_black_list_entry *)kmem_zalloc (sizeof (struct fcp_black_list_entry), KM_SLEEP); bcopy(&wwn, &new_entry->wwn, sizeof (la_wwn_t)); new_entry->lun = lun_id; new_entry->masked = 0; new_entry->next = *pplun_blacklist; *pplun_blacklist = new_entry; } /* * Check if we should mask the specified lun of this fcp_tgt */ static int fcp_should_mask(la_wwn_t *wwn, uint32_t lun_id) { struct fcp_black_list_entry *remote_port; remote_port = fcp_lun_blacklist; while (remote_port != NULL) { if (bcmp(wwn, &remote_port->wwn, sizeof (la_wwn_t)) == 0) { if (remote_port->lun == lun_id) { remote_port->masked++; if (remote_port->masked == 1) { fcp_log(CE_NOTE, NULL, "LUN %d of port " "%02x%02x%02x%02x%02x%02x%02x%02x " "is masked due to black listing.\n", lun_id, wwn->raw_wwn[0], wwn->raw_wwn[1], wwn->raw_wwn[2], wwn->raw_wwn[3], wwn->raw_wwn[4], wwn->raw_wwn[5], wwn->raw_wwn[6], wwn->raw_wwn[7]); } return (TRUE); } } remote_port = remote_port->next; } return (FALSE); } /* * Release all allocated resources */ static void fcp_cleanup_blacklist(struct fcp_black_list_entry **pplun_blacklist) { struct fcp_black_list_entry *tmp_entry = *pplun_blacklist; struct fcp_black_list_entry *current_entry = NULL; ASSERT(mutex_owned(&fcp_global_mutex)); /* * Traverse all luns */ while (tmp_entry) { current_entry = tmp_entry; tmp_entry = tmp_entry->next; kmem_free(current_entry, sizeof (struct fcp_black_list_entry)); } *pplun_blacklist = NULL; }