int set_link_state()

in hw/hfi1/chip.c [10666:10965]


int set_link_state(struct hfi1_pportdata *ppd, u32 state)
{
	struct hfi1_devdata *dd = ppd->dd;
	struct ib_event event = {.device = NULL};
	int ret1, ret = 0;
	int orig_new_state, poll_bounce;

	mutex_lock(&ppd->hls_lock);

	orig_new_state = state;
	if (state == HLS_DN_DOWNDEF)
		state = HLS_DEFAULT;

	/* interpret poll -> poll as a link bounce */
	poll_bounce = ppd->host_link_state == HLS_DN_POLL &&
		      state == HLS_DN_POLL;

	dd_dev_info(dd, "%s: current %s, new %s %s%s\n", __func__,
		    link_state_name(ppd->host_link_state),
		    link_state_name(orig_new_state),
		    poll_bounce ? "(bounce) " : "",
		    link_state_reason_name(ppd, state));

	/*
	 * If we're going to a (HLS_*) link state that implies the logical
	 * link state is neither of (IB_PORT_ARMED, IB_PORT_ACTIVE), then
	 * reset is_sm_config_started to 0.
	 */
	if (!(state & (HLS_UP_ARMED | HLS_UP_ACTIVE)))
		ppd->is_sm_config_started = 0;

	/*
	 * Do nothing if the states match.  Let a poll to poll link bounce
	 * go through.
	 */
	if (ppd->host_link_state == state && !poll_bounce)
		goto done;

	switch (state) {
	case HLS_UP_INIT:
		if (ppd->host_link_state == HLS_DN_POLL &&
		    (quick_linkup || dd->icode == ICODE_FUNCTIONAL_SIMULATOR)) {
			/*
			 * Quick link up jumps from polling to here.
			 *
			 * Whether in normal or loopback mode, the
			 * simulator jumps from polling to link up.
			 * Accept that here.
			 */
			/* OK */
		} else if (ppd->host_link_state != HLS_GOING_UP) {
			goto unexpected;
		}

		/*
		 * Wait for Link_Up physical state.
		 * Physical and Logical states should already be
		 * be transitioned to LinkUp and LinkInit respectively.
		 */
		ret = wait_physical_linkstate(ppd, PLS_LINKUP, 1000);
		if (ret) {
			dd_dev_err(dd,
				   "%s: physical state did not change to LINK-UP\n",
				   __func__);
			break;
		}

		ret = wait_logical_linkstate(ppd, IB_PORT_INIT, 1000);
		if (ret) {
			dd_dev_err(dd,
				   "%s: logical state did not change to INIT\n",
				   __func__);
			break;
		}

		/* clear old transient LINKINIT_REASON code */
		if (ppd->linkinit_reason >= OPA_LINKINIT_REASON_CLEAR)
			ppd->linkinit_reason =
				OPA_LINKINIT_REASON_LINKUP;

		/* enable the port */
		add_rcvctrl(dd, RCV_CTRL_RCV_PORT_ENABLE_SMASK);

		handle_linkup_change(dd, 1);
		pio_kernel_linkup(dd);

		/*
		 * After link up, a new link width will have been set.
		 * Update the xmit counters with regards to the new
		 * link width.
		 */
		update_xmit_counters(ppd, ppd->link_width_active);

		ppd->host_link_state = HLS_UP_INIT;
		update_statusp(ppd, IB_PORT_INIT);
		break;
	case HLS_UP_ARMED:
		if (ppd->host_link_state != HLS_UP_INIT)
			goto unexpected;

		if (!data_vls_operational(ppd)) {
			dd_dev_err(dd,
				   "%s: Invalid data VL credits or mtu\n",
				   __func__);
			ret = -EINVAL;
			break;
		}

		set_logical_state(dd, LSTATE_ARMED);
		ret = wait_logical_linkstate(ppd, IB_PORT_ARMED, 1000);
		if (ret) {
			dd_dev_err(dd,
				   "%s: logical state did not change to ARMED\n",
				   __func__);
			break;
		}
		ppd->host_link_state = HLS_UP_ARMED;
		update_statusp(ppd, IB_PORT_ARMED);
		/*
		 * The simulator does not currently implement SMA messages,
		 * so neighbor_normal is not set.  Set it here when we first
		 * move to Armed.
		 */
		if (dd->icode == ICODE_FUNCTIONAL_SIMULATOR)
			ppd->neighbor_normal = 1;
		break;
	case HLS_UP_ACTIVE:
		if (ppd->host_link_state != HLS_UP_ARMED)
			goto unexpected;

		set_logical_state(dd, LSTATE_ACTIVE);
		ret = wait_logical_linkstate(ppd, IB_PORT_ACTIVE, 1000);
		if (ret) {
			dd_dev_err(dd,
				   "%s: logical state did not change to ACTIVE\n",
				   __func__);
		} else {
			/* tell all engines to go running */
			sdma_all_running(dd);
			ppd->host_link_state = HLS_UP_ACTIVE;
			update_statusp(ppd, IB_PORT_ACTIVE);

			/* Signal the IB layer that the port has went active */
			event.device = &dd->verbs_dev.rdi.ibdev;
			event.element.port_num = ppd->port;
			event.event = IB_EVENT_PORT_ACTIVE;
		}
		break;
	case HLS_DN_POLL:
		if ((ppd->host_link_state == HLS_DN_DISABLE ||
		     ppd->host_link_state == HLS_DN_OFFLINE) &&
		    dd->dc_shutdown)
			dc_start(dd);
		/* Hand LED control to the DC */
		write_csr(dd, DCC_CFG_LED_CNTRL, 0);

		if (ppd->host_link_state != HLS_DN_OFFLINE) {
			u8 tmp = ppd->link_enabled;

			ret = goto_offline(ppd, ppd->remote_link_down_reason);
			if (ret) {
				ppd->link_enabled = tmp;
				break;
			}
			ppd->remote_link_down_reason = 0;

			if (ppd->driver_link_ready)
				ppd->link_enabled = 1;
		}

		set_all_slowpath(ppd->dd);
		ret = set_local_link_attributes(ppd);
		if (ret)
			break;

		ppd->port_error_action = 0;

		if (quick_linkup) {
			/* quick linkup does not go into polling */
			ret = do_quick_linkup(dd);
		} else {
			ret1 = set_physical_link_state(dd, PLS_POLLING);
			if (!ret1)
				ret1 = wait_phys_link_out_of_offline(ppd,
								     3000);
			if (ret1 != HCMD_SUCCESS) {
				dd_dev_err(dd,
					   "Failed to transition to Polling link state, return 0x%x\n",
					   ret1);
				ret = -EINVAL;
			}
		}

		/*
		 * Change the host link state after requesting DC8051 to
		 * change its physical state so that we can ignore any
		 * interrupt with stale LNI(XX) error, which will not be
		 * cleared until DC8051 transitions to Polling state.
		 */
		ppd->host_link_state = HLS_DN_POLL;
		ppd->offline_disabled_reason =
			HFI1_ODR_MASK(OPA_LINKDOWN_REASON_NONE);
		/*
		 * If an error occurred above, go back to offline.  The
		 * caller may reschedule another attempt.
		 */
		if (ret)
			goto_offline(ppd, 0);
		else
			log_physical_state(ppd, PLS_POLLING);
		break;
	case HLS_DN_DISABLE:
		/* link is disabled */
		ppd->link_enabled = 0;

		/* allow any state to transition to disabled */

		/* must transition to offline first */
		if (ppd->host_link_state != HLS_DN_OFFLINE) {
			ret = goto_offline(ppd, ppd->remote_link_down_reason);
			if (ret)
				break;
			ppd->remote_link_down_reason = 0;
		}

		if (!dd->dc_shutdown) {
			ret1 = set_physical_link_state(dd, PLS_DISABLED);
			if (ret1 != HCMD_SUCCESS) {
				dd_dev_err(dd,
					   "Failed to transition to Disabled link state, return 0x%x\n",
					   ret1);
				ret = -EINVAL;
				break;
			}
			ret = wait_physical_linkstate(ppd, PLS_DISABLED, 10000);
			if (ret) {
				dd_dev_err(dd,
					   "%s: physical state did not change to DISABLED\n",
					   __func__);
				break;
			}
			dc_shutdown(dd);
		}
		ppd->host_link_state = HLS_DN_DISABLE;
		break;
	case HLS_DN_OFFLINE:
		if (ppd->host_link_state == HLS_DN_DISABLE)
			dc_start(dd);

		/* allow any state to transition to offline */
		ret = goto_offline(ppd, ppd->remote_link_down_reason);
		if (!ret)
			ppd->remote_link_down_reason = 0;
		break;
	case HLS_VERIFY_CAP:
		if (ppd->host_link_state != HLS_DN_POLL)
			goto unexpected;
		ppd->host_link_state = HLS_VERIFY_CAP;
		log_physical_state(ppd, PLS_CONFIGPHY_VERIFYCAP);
		break;
	case HLS_GOING_UP:
		if (ppd->host_link_state != HLS_VERIFY_CAP)
			goto unexpected;

		ret1 = set_physical_link_state(dd, PLS_LINKUP);
		if (ret1 != HCMD_SUCCESS) {
			dd_dev_err(dd,
				   "Failed to transition to link up state, return 0x%x\n",
				   ret1);
			ret = -EINVAL;
			break;
		}
		ppd->host_link_state = HLS_GOING_UP;
		break;

	case HLS_GOING_OFFLINE:		/* transient within goto_offline() */
	case HLS_LINK_COOLDOWN:		/* transient within goto_offline() */
	default:
		dd_dev_info(dd, "%s: state 0x%x: not supported\n",
			    __func__, state);
		ret = -EINVAL;
		break;
	}

	goto done;

unexpected:
	dd_dev_err(dd, "%s: unexpected state transition from %s to %s\n",
		   __func__, link_state_name(ppd->host_link_state),
		   link_state_name(state));
	ret = -EINVAL;

done:
	mutex_unlock(&ppd->hls_lock);

	if (event.device)
		ib_dispatch_event(&event);

	return ret;
}