aboutsummaryrefslogtreecommitdiff
path: root/hw/fsp/fsp-dpo.c
blob: 0796d9ae935bd2a905ec746f7cebdda53228f780 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// SPDX-License-Identifier: Apache-2.0
/*
 * FSP DPO (Delayed Power Off) event support
 *
 * Copyright 2013-2017 IBM Corp.
 */

#define pr_fmt(fmt) "FSP-DPO: " fmt

#include <skiboot.h>
#include <fsp.h>
#include <stdio.h>
#include <timebase.h>
#include <opal.h>
#include <opal-msg.h>

#define DPO_CMD_SGN_BYTE0	0xf4 /* Byte[0] signature */
#define DPO_CMD_SGN_BYTE1	0x20 /* Byte[1] signature */
#define DPO_TIMEOUT		2700 /* 45 minutes in seconds */

bool fsp_dpo_pending;
static unsigned long fsp_dpo_init_tb;

/*
 * OPAL DPO interface
 *
 * Returns zero if DPO is not active, positive value indicating number
 * of seconds remaining for a forced system shutdown. This will enable
 * the host to schedule for shutdown voluntarily before timeout occurs.
 */
static int64_t fsp_opal_get_dpo_status(int64_t *dpo_timeout)
{
	if (!fsp_dpo_pending) {
		*dpo_timeout = 0;
		return OPAL_WRONG_STATE;
	}

	*dpo_timeout = DPO_TIMEOUT - tb_to_secs(mftb() - fsp_dpo_init_tb);
	return OPAL_SUCCESS;
}

/* Process FSP DPO init message */
static void fsp_process_dpo(struct fsp_msg *msg)
{
	struct fsp_msg *resp;
	u32 cmd = FSP_RSP_INIT_DPO;
	int rc;

	/* DPO message does not have the correct signatures */
	if ((msg->data.bytes[0] != DPO_CMD_SGN_BYTE0)
			|| (msg->data.bytes[1] != DPO_CMD_SGN_BYTE1)) {
		prerror("Message signatures did not match\n");
		cmd |= FSP_STATUS_INVALID_CMD;
		resp = fsp_mkmsg(cmd, 0);
		if (resp == NULL) {
			prerror("%s : Message allocation failed\n", __func__);
			return;
		}
		if (fsp_queue_msg(resp, fsp_freemsg)) {
			fsp_freemsg(resp);
			prerror("%s : Failed to queue response "
				"message\n", __func__);
		}
		return;
	}

	/* OPAL is already in "DPO pending" state */
	if (fsp_dpo_pending) {
		prlog(PR_INFO, "OPAL already in DPO pending state\n");
		cmd |= FSP_STATUS_INVALID_DPOSTATE;
		resp = fsp_mkmsg(cmd, 0);
		if (resp == NULL) {
			prerror("%s : Message allocation failed\n", __func__);
			return;
		}
		if (fsp_queue_msg(resp, fsp_freemsg)) {
			fsp_freemsg(resp);
			prerror("%s : Failed to queue response "
				"message\n", __func__);
		}
		return;
	}


	/* Inform the host about DPO */
	rc = opal_queue_msg(OPAL_MSG_DPO, NULL, NULL);
	if (rc) {
		prerror("OPAL message queuing failed\n");
		cmd |= FSP_STATUS_GENERIC_ERROR;
		resp = fsp_mkmsg(cmd, 0);
		if (resp == NULL) {
			prerror("%s : Message allocation failed\n", __func__);
			return;
		}
		if (fsp_queue_msg(resp, fsp_freemsg)) {
			fsp_freemsg(resp);
			prerror("%s : Failed to queue response "
				"message\n", __func__);
		}
		return;
	} else
		prlog(PR_INFO, "Notified host about DPO event\n");

	/* Acknowledge the FSP on DPO */
	resp = fsp_mkmsg(cmd, 0);
	if (resp == NULL) {
		prerror("%s : Message allocation failed\n", __func__);
		return;
	}
	if (fsp_queue_msg(resp, fsp_freemsg)) {
		fsp_freemsg(resp);
		prerror("%s : Failed to queue response message\n", __func__);
		return;
	}

	/* Record DPO init time and set DPO pending flag */
	fsp_dpo_init_tb = mftb();
	fsp_dpo_pending = true;

	/*
	 * OPAL is now in DPO pending state. After first detecting DPO
	 * condition from OPAL, the host will have 45 minutes to prepare
	 * the system for shutdown. The host must take all necessary actions
	 * required in that regard and at the end shutdown itself. The host
	 * shutdown sequence eventually will make the call OPAL_CEC_POWER_DOWN
	 * which in turn ask the FSP to shutdown the CEC. If the FSP does not
	 * receive the cec power down command from OPAL within 45 minutes,
	 * it will assume that the host and the OPAL has processed the DPO
	 * sequence successfully and hence force power off the system.
	 */
}

/* Handle DPO sub-command from FSP */
static bool fsp_dpo_message(u32 cmd_sub_mod, struct fsp_msg *msg)
{
	if (cmd_sub_mod == FSP_CMD_INIT_DPO) {
		prlog(PR_INFO, "Delayed Power Off (DPO) notification received\n");
		fsp_process_dpo(msg);
		return true;
	}

	return false;
}

static struct fsp_client fsp_dpo_client = {
	.message = fsp_dpo_message,
};

void fsp_dpo_init(void)
{
	fsp_register_client(&fsp_dpo_client, FSP_MCLASS_SERVICE);
	opal_register(OPAL_GET_DPO_STATUS, fsp_opal_get_dpo_status, 1);
	prlog(PR_INFO, "FSP DPO support initialized\n");
}