1 // SPDX-License-Identifier: GPL-2.0+
2 // Copyright IBM Corp 2019
4 #include <linux/device.h>
5 #include <linux/errno.h>
6 #include <linux/slab.h>
7 #include <linux/fsi-occ.h>
9 #include <linux/module.h>
10 #include <linux/mod_devicetable.h>
11 #include <linux/mutex.h>
12 #include <linux/platform_device.h>
13 #include <linux/string.h>
14 #include <linux/sysfs.h>
18 #define OCC_CHECKSUM_RETRIES 3
26 struct mutex sbe_error_lock
; /* lock access to ffdc data */
30 #define to_p9_sbe_occ(x) container_of((x), struct p9_sbe_occ, occ)
32 static ssize_t
ffdc_read(struct file
*filp
, struct kobject
*kobj
,
33 struct bin_attribute
*battr
, char *buf
, loff_t pos
,
37 struct occ
*occ
= dev_get_drvdata(kobj_to_dev(kobj
));
38 struct p9_sbe_occ
*ctx
= to_p9_sbe_occ(occ
);
40 mutex_lock(&ctx
->sbe_error_lock
);
42 rc
= memory_read_from_buffer(buf
, count
, &pos
, ctx
->ffdc
,
44 if (pos
>= ctx
->ffdc_len
)
45 ctx
->sbe_error
= false;
47 mutex_unlock(&ctx
->sbe_error_lock
);
51 static BIN_ATTR_RO(ffdc
, OCC_MAX_RESP_WORDS
* 4);
53 static bool p9_sbe_occ_save_ffdc(struct p9_sbe_occ
*ctx
, const void *resp
,
58 mutex_lock(&ctx
->sbe_error_lock
);
59 if (!ctx
->sbe_error
) {
60 if (resp_len
> ctx
->ffdc_size
) {
62 ctx
->ffdc
= kvmalloc(resp_len
, GFP_KERNEL
);
69 ctx
->ffdc_size
= resp_len
;
73 ctx
->sbe_error
= true;
74 ctx
->ffdc_len
= resp_len
;
75 memcpy(ctx
->ffdc
, resp
, resp_len
);
79 mutex_unlock(&ctx
->sbe_error_lock
);
83 static int p9_sbe_occ_send_cmd(struct occ
*occ
, u8
*cmd
, size_t len
,
84 void *resp
, size_t resp_len
)
86 size_t original_resp_len
= resp_len
;
87 struct p9_sbe_occ
*ctx
= to_p9_sbe_occ(occ
);
90 for (i
= 0; i
< OCC_CHECKSUM_RETRIES
; ++i
) {
91 rc
= fsi_occ_submit(ctx
->sbe
, cmd
, len
, resp
, &resp_len
);
95 if (p9_sbe_occ_save_ffdc(ctx
, resp
, resp_len
))
96 sysfs_notify(&occ
->bus_dev
->kobj
, NULL
,
97 bin_attr_ffdc
.attr
.name
);
102 resp_len
= original_resp_len
;
105 switch (((struct occ_response
*)resp
)->return_status
) {
106 case OCC_RESP_CMD_IN_PRG
:
109 case OCC_RESP_SUCCESS
:
112 case OCC_RESP_CMD_INVAL
:
113 case OCC_RESP_CMD_LEN_INVAL
:
114 case OCC_RESP_DATA_INVAL
:
115 case OCC_RESP_CHKSUM_ERR
:
118 case OCC_RESP_INT_ERR
:
119 case OCC_RESP_BAD_STATE
:
120 case OCC_RESP_CRIT_EXCEPT
:
121 case OCC_RESP_CRIT_INIT
:
122 case OCC_RESP_CRIT_WATCHDOG
:
123 case OCC_RESP_CRIT_OCB
:
124 case OCC_RESP_CRIT_HW
:
134 static int p9_sbe_occ_probe(struct platform_device
*pdev
)
138 struct p9_sbe_occ
*ctx
= devm_kzalloc(&pdev
->dev
, sizeof(*ctx
),
143 mutex_init(&ctx
->sbe_error_lock
);
145 ctx
->sbe
= pdev
->dev
.parent
;
147 occ
->bus_dev
= &pdev
->dev
;
148 platform_set_drvdata(pdev
, occ
);
150 occ
->powr_sample_time_us
= 500;
151 occ
->poll_cmd_data
= 0x20; /* P9 OCC poll data */
152 occ
->send_cmd
= p9_sbe_occ_send_cmd
;
155 if (rc
== -ESHUTDOWN
)
156 rc
= -ENODEV
; /* Host is shutdown, don't spew errors */
159 rc
= device_create_bin_file(occ
->bus_dev
, &bin_attr_ffdc
);
161 dev_warn(occ
->bus_dev
,
162 "failed to create SBE error ffdc file\n");
170 static void p9_sbe_occ_remove(struct platform_device
*pdev
)
172 struct occ
*occ
= platform_get_drvdata(pdev
);
173 struct p9_sbe_occ
*ctx
= to_p9_sbe_occ(occ
);
175 device_remove_bin_file(occ
->bus_dev
, &bin_attr_ffdc
);
183 static const struct of_device_id p9_sbe_occ_of_match
[] = {
184 { .compatible
= "ibm,p9-occ-hwmon" },
185 { .compatible
= "ibm,p10-occ-hwmon" },
188 MODULE_DEVICE_TABLE(of
, p9_sbe_occ_of_match
);
190 static struct platform_driver p9_sbe_occ_driver
= {
193 .of_match_table
= p9_sbe_occ_of_match
,
195 .probe
= p9_sbe_occ_probe
,
196 .remove
= p9_sbe_occ_remove
,
199 module_platform_driver(p9_sbe_occ_driver
);
201 MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
202 MODULE_DESCRIPTION("BMC P9 OCC hwmon driver");
203 MODULE_LICENSE("GPL");