for (i = 0, offset = 0; i < desc->out_num; i++) {
u32 out_size = nd_cmd_out_size(nvdimm, cmd, desc, i, buf,
- (u32 *) out_obj->buffer.pointer);
+ (u32 *) out_obj->buffer.pointer,
+ out_obj->buffer.length - offset);
if (offset + out_size > out_obj->buffer.length) {
dev_dbg(dev, "%s:%s output object underflow cmd: %s field: %d\n",
u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
const struct nd_cmd_desc *desc, int idx, const u32 *in_field,
- const u32 *out_field)
+ const u32 *out_field, unsigned long remainder)
{
if (idx >= desc->out_num)
return UINT_MAX;
return in_field[1];
else if (nvdimm && cmd == ND_CMD_VENDOR && idx == 2)
return out_field[1];
- else if (!nvdimm && cmd == ND_CMD_ARS_STATUS && idx == 2)
- return out_field[1] - 8;
- else if (cmd == ND_CMD_CALL) {
+ else if (!nvdimm && cmd == ND_CMD_ARS_STATUS && idx == 2) {
+ /*
+ * Per table 9-276 ARS Data in ACPI 6.1, out_field[1] is
+ * "Size of Output Buffer in bytes, including this
+ * field."
+ */
+ if (out_field[1] < 4)
+ return 0;
+ /*
+ * ACPI 6.1 is ambiguous if 'status' is included in the
+ * output size. If we encounter an output size that
+ * overshoots the remainder by 4 bytes, assume it was
+ * including 'status'.
+ */
+ if (out_field[1] - 8 == remainder)
+ return remainder;
+ return out_field[1] - 4;
+ } else if (cmd == ND_CMD_CALL) {
struct nd_cmd_pkg *pkg = (struct nd_cmd_pkg *) in_field;
return pkg->nd_size_out;
/* process an output envelope */
for (i = 0; i < desc->out_num; i++) {
u32 out_size = nd_cmd_out_size(nvdimm, cmd, desc, i,
- (u32 *) in_env, (u32 *) out_env);
+ (u32 *) in_env, (u32 *) out_env, 0);
u32 copy;
if (out_size == UINT_MAX) {
const struct nd_cmd_desc *desc, int idx, void *buf);
u32 nd_cmd_out_size(struct nvdimm *nvdimm, int cmd,
const struct nd_cmd_desc *desc, int idx, const u32 *in_field,
- const u32 *out_field);
+ const u32 *out_field, unsigned long remainder);
int nvdimm_bus_check_dimm_count(struct nvdimm_bus *nvdimm_bus, int dimm_count);
struct nd_region *nvdimm_pmem_region_create(struct nvdimm_bus *nvdimm_bus,
struct nd_region_desc *ndr_desc);