diff --git a/src/include/ipxe/fc.h b/src/include/ipxe/fc.h index 06d38baf..6689f394 100644 --- a/src/include/ipxe/fc.h +++ b/src/include/ipxe/fc.h @@ -455,6 +455,13 @@ struct fc_ulp_user { struct fc_ulp *ulp; /** List of users */ struct list_head list; + /** Containing object reference count, or NULL */ + struct refcnt *refcnt; + /** Examine link state + * + * @v user Fibre Channel upper-layer-protocol user + */ + void ( * examine ) ( struct fc_ulp_user *user ); }; /** @@ -479,6 +486,43 @@ fc_ulp_put ( struct fc_ulp *ulp ) { ref_put ( &ulp->refcnt ); } +/** + * Get reference to Fibre Channel upper-layer protocol user + * + * @v user Fibre Channel upper-layer protocol user + * @ret user Fibre Channel upper-layer protocol user + */ +static inline __attribute__ (( always_inline )) struct fc_ulp_user * +fc_ulp_user_get ( struct fc_ulp_user *user ) { + ref_get ( user->refcnt ); + return user; +} + +/** + * Drop reference to Fibre Channel upper-layer protocol user + * + * @v user Fibre Channel upper-layer protocol user + */ +static inline __attribute__ (( always_inline )) void +fc_ulp_user_put ( struct fc_ulp_user *user ) { + ref_put ( user->refcnt ); +} + +/** + * Initialise Fibre Channel upper-layer protocol user + * + * @v user Fibre Channel upper-layer protocol user + * @v examine Examine link state method + * @v refcnt Containing object reference count, or NULL + */ +static inline __attribute__ (( always_inline )) void +fc_ulp_user_init ( struct fc_ulp_user *user, + void ( * examine ) ( struct fc_ulp_user *user ), + struct refcnt *refcnt ) { + user->examine = examine; + user->refcnt = refcnt; +} + extern struct fc_ulp * fc_ulp_get_wwn_type ( const struct fc_name *port_wwn, unsigned int type ); extern struct fc_ulp * diff --git a/src/net/fc.c b/src/net/fc.c index a94456c8..977ad07c 100644 --- a/src/net/fc.c +++ b/src/net/fc.c @@ -1651,6 +1651,8 @@ void fc_ulp_detach ( struct fc_ulp_user *user ) { */ int fc_ulp_login ( struct fc_ulp *ulp, const void *param, size_t param_len, int originated ) { + struct fc_ulp_user *user; + struct fc_ulp_user *tmp; /* Perform implicit logout if logged in and service parameters differ */ if ( fc_link_ok ( &ulp->link ) && @@ -1659,6 +1661,22 @@ int fc_ulp_login ( struct fc_ulp *ulp, const void *param, size_t param_len, fc_ulp_logout ( ulp, 0 ); } + /* Work around a bug in some versions of the Linux Fibre + * Channel stack, which fail to fully initialise image pairs + * established via a PRLI originated by the Linux stack + * itself. + */ + if ( originated ) + ulp->flags |= FC_ULP_ORIGINATED_LOGIN_OK; + if ( ! ( ulp->flags & FC_ULP_ORIGINATED_LOGIN_OK ) ) { + DBGC ( ulp, "FCULP %s/%02x sending extra PRLI to work around " + "Linux bug\n", + fc_ntoa ( &ulp->peer->port_wwn ), ulp->type ); + fc_link_stop ( &ulp->link ); + fc_link_start ( &ulp->link ); + return 0; + } + /* Log in, if applicable */ if ( ! fc_link_ok ( &ulp->link ) ) { @@ -1685,18 +1703,11 @@ int fc_ulp_login ( struct fc_ulp *ulp, const void *param, size_t param_len, /* Record login */ fc_link_up ( &ulp->link ); - /* Work around a bug in some versions of the Linux Fibre - * Channel stack, which fail to fully initialise image pairs - * established via a PRLI originated by the Linux stack - * itself. - */ - if ( originated ) - ulp->flags |= FC_ULP_ORIGINATED_LOGIN_OK; - if ( ! ( ulp->flags & FC_ULP_ORIGINATED_LOGIN_OK ) ) { - DBGC ( ulp, "FCULP %s/%02x sending extra PRLI to work around " - "Linux bug\n", - fc_ntoa ( &ulp->peer->port_wwn ), ulp->type ); - fc_link_start ( &ulp->link ); + /* Notify users of link state change */ + list_for_each_entry_safe ( user, tmp, &ulp->users, list ) { + fc_ulp_user_get ( user ); + user->examine ( user ); + fc_ulp_user_put ( user ); } return 0; @@ -1709,6 +1720,8 @@ int fc_ulp_login ( struct fc_ulp *ulp, const void *param, size_t param_len, * @v rc Reason for logout */ void fc_ulp_logout ( struct fc_ulp *ulp, int rc ) { + struct fc_ulp_user *user; + struct fc_ulp_user *tmp; DBGC ( ulp, "FCULP %s/%02x logged out: %s\n", fc_ntoa ( &ulp->peer->port_wwn ), ulp->type, strerror ( rc ) ); @@ -1726,6 +1739,13 @@ void fc_ulp_logout ( struct fc_ulp *ulp, int rc ) { /* Record logout */ fc_link_err ( &ulp->link, rc ); + /* Notify users of link state change */ + list_for_each_entry_safe ( user, tmp, &ulp->users, list ) { + fc_ulp_user_get ( user ); + user->examine ( user ); + fc_ulp_user_put ( user ); + } + /* Close ULP if there are no clients attached */ if ( list_empty ( &ulp->users ) ) fc_ulp_close ( ulp, rc ); diff --git a/src/net/fcp.c b/src/net/fcp.c index 28d2095d..419fea3e 100644 --- a/src/net/fcp.c +++ b/src/net/fcp.c @@ -920,6 +920,26 @@ static struct interface_operation fcpdev_scsi_op[] = { static struct interface_descriptor fcpdev_scsi_desc = INTF_DESC ( struct fcp_device, scsi, fcpdev_scsi_op ); +/** + * Examine FCP ULP link state + * + * @v user Fibre Channel upper-layer protocol user + */ +static void fcpdev_examine ( struct fc_ulp_user *user ) { + struct fcp_device *fcpdev = + container_of ( user, struct fcp_device, user ); + + if ( fc_link_ok ( &fcpdev->user.ulp->link ) ) { + DBGC ( fcpdev, "FCP %p link is up\n", fcpdev ); + } else { + DBGC ( fcpdev, "FCP %p link is down: %s\n", + fcpdev, strerror ( fcpdev->user.ulp->link.rc ) ); + } + + /* Notify SCSI layer of window change */ + xfer_window_changed ( &fcpdev->scsi ); +} + /** * Open FCP device * @@ -950,10 +970,13 @@ static int fcpdev_open ( struct interface *parent, struct fc_name *wwn, ref_init ( &fcpdev->refcnt, NULL ); intf_init ( &fcpdev->scsi, &fcpdev_scsi_desc, &fcpdev->refcnt ); INIT_LIST_HEAD ( &fcpdev->fcpcmds ); - fc_ulp_attach ( ulp, &fcpdev->user ); + fc_ulp_user_init ( &fcpdev->user, fcpdev_examine, &fcpdev->refcnt ); DBGC ( fcpdev, "FCP %p opened for %s\n", fcpdev, fc_ntoa ( wwn ) ); + /* Attach to Fibre Channel ULP */ + fc_ulp_attach ( ulp, &fcpdev->user ); + /* Preserve parameters required for boot firmware table */ memcpy ( &fcpdev->wwn, wwn, sizeof ( fcpdev->wwn ) ); memcpy ( &fcpdev->lun, lun, sizeof ( fcpdev->lun ) );