blob: 36ddbb69ba2aa24aad8de537c82d0e0ed08798db [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2022 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 COREDUMP layer HSSPI Protocol
*/
#include "qm35.h"
#include "hsspi_coredump.h"
#define COREDUMP_HEADER_NTF 0x00
#define COREDUMP_BODY_NTF 0x01
#define COREDUMP_RCV_STATUS 0x02
#define COREDUMP_FORCE_CMD 0x03
#define COREDUMP_RCV_NACK 0x00
#define COREDUMP_RCV_ACK 0x01
#define COREDUMP_RCV_TIMER_TIMEOUT_S 2
struct __packed coredump_common_hdr {
uint8_t cmd_id;
};
struct __packed coredump_hdr_ntf {
uint32_t size;
uint16_t crc;
};
struct __packed coredump_rcv_status {
uint8_t ack;
};
struct coredump_packet *coredump_packet_alloc(u16 length)
{
struct coredump_packet *p;
p = kzalloc(sizeof(*p), GFP_KERNEL);
if (!p)
return NULL;
if (hsspi_init_block(&p->blk, length)) {
kfree(p);
return NULL;
}
return p;
}
void coredump_packet_free(struct coredump_packet *p)
{
hsspi_deinit_block(&p->blk);
kfree(p);
}
static int coredump_send_rcv_status(struct coredump_layer *layer, uint8_t ack)
{
struct coredump_packet *p;
struct coredump_common_hdr hdr;
struct coredump_rcv_status rcv;
struct qm35_ctx *qm35_hdl;
pr_info("qm35: coredump: sending status %s\n",
layer->coredump_status == COREDUMP_RCV_ACK ? "ACK" : "NACK");
qm35_hdl = container_of(layer, struct qm35_ctx, coredump_layer);
p = coredump_packet_alloc(sizeof(hdr) + sizeof(rcv));
if (!p)
return -ENOMEM;
hdr.cmd_id = COREDUMP_RCV_STATUS;
rcv.ack = ack;
memcpy(p->blk.data, &hdr, sizeof(hdr));
memcpy(p->blk.data + sizeof(hdr), &rcv, sizeof(rcv));
return hsspi_send(&qm35_hdl->hsspi, &qm35_hdl->coredump_layer.hlayer,
&p->blk);
}
static uint16_t coredump_get_checksum(struct coredump_layer *layer)
{
uint32_t idx;
uint16_t crc = 0;
for (idx = 0; idx < layer->coredump_data_wr_idx; idx++) {
uint8_t *val;
val = layer->coredump_data + idx;
crc += *val;
}
return crc;
}
static void corredump_on_expired_timer(struct timer_list *timer)
{
struct coredump_layer *layer =
container_of(timer, struct coredump_layer, timer);
pr_warn("qm35: coredump receive timer expired\n");
coredump_send_rcv_status(layer, layer->coredump_status);
}
static int coredump_registered(struct hsspi_layer *hlayer)
{
return 0;
}
static void coredump_unregistered(struct hsspi_layer *hlayer)
{
;
}
static struct hsspi_block *coredump_get(struct hsspi_layer *hlayer, u16 length)
{
struct coredump_packet *p;
p = coredump_packet_alloc(length);
if (!p)
return NULL;
return &p->blk;
}
static void coredump_header_ntf_received(struct coredump_layer *layer,
struct coredump_hdr_ntf chn)
{
void *data;
pr_info("qm35: coredump: receiving coredump with len: %d and crc: 0x%x\n",
chn.size, chn.crc);
layer->coredump_data_wr_idx = 0;
layer->coredump_size = chn.size;
layer->coredump_crc = chn.crc;
layer->coredump_status = COREDUMP_RCV_NACK;
data = krealloc(layer->coredump_data, layer->coredump_size, GFP_KERNEL);
if (ZERO_OR_NULL_PTR(data)) {
layer->coredump_data = NULL;
layer->coredump_size = 0;
layer->coredump_crc = 0;
pr_err("qm35: failed to allocate coredump mem: %px\n", data);
} else {
layer->coredump_data = data;
}
}
static int coredump_body_ntf_received(struct coredump_layer *layer,
uint8_t *cch_body, uint16_t cch_body_size)
{
if (!layer->coredump_data) {
pr_err("qm35: failed to save coredump, mem not allocated\n");
return 1;
}
if (cch_body_size + layer->coredump_data_wr_idx >
layer->coredump_size) {
pr_err("qm35: failed to save coredump, mem overflow: max size: %d, wr_idx: %d, cd size: %d\n",
layer->coredump_size, layer->coredump_data_wr_idx,
cch_body_size);
return 1;
}
memcpy(layer->coredump_data + layer->coredump_data_wr_idx, cch_body,
cch_body_size);
layer->coredump_data_wr_idx += cch_body_size;
return 0;
}
static void coredump_received(struct hsspi_layer *hlayer,
struct hsspi_block *blk, int status)
{
struct coredump_common_hdr cch;
struct coredump_hdr_ntf chn;
uint8_t *cch_body;
uint16_t cch_body_size;
struct coredump_packet *packet =
container_of(blk, struct coredump_packet, blk);
struct coredump_layer *layer =
container_of(hlayer, struct coredump_layer, hlayer);
if (status)
goto out;
if (blk->length < sizeof(struct coredump_common_hdr)) {
pr_err("qm35: coredump packet header too small: %d bytes\n",
blk->length);
goto out;
}
del_timer_sync(&layer->timer);
memcpy(&cch, blk->data, sizeof(cch));
cch_body = blk->data + sizeof(cch);
cch_body_size = blk->length - sizeof(cch);
switch (cch.cmd_id) {
case COREDUMP_HEADER_NTF:
if (cch_body_size < sizeof(chn)) {
pr_err("qm35: coredump packet header ntf too small: %d bytes\n",
cch_body_size);
break;
}
memcpy(&chn, cch_body, sizeof(chn));
coredump_header_ntf_received(layer, chn);
break;
case COREDUMP_BODY_NTF:
if (coredump_body_ntf_received(layer, cch_body, cch_body_size))
break;
if (layer->coredump_data_wr_idx == layer->coredump_size) {
uint16_t crc = coredump_get_checksum(layer);
pr_info("qm35: coredump: calculated crc: 0x%x, header crc: 0x%x\n",
crc, layer->coredump_crc);
if (crc == layer->coredump_crc)
layer->coredump_status = COREDUMP_RCV_ACK;
coredump_send_rcv_status(layer, layer->coredump_status);
break;
}
mod_timer(&layer->timer,
jiffies + COREDUMP_RCV_TIMER_TIMEOUT_S * HZ);
break;
default:
pr_err("qm35: coredump: wrong cmd id received: 0x%x\n",
cch.cmd_id);
break;
}
out:
coredump_packet_free(packet);
}
static void coredump_sent(struct hsspi_layer *hlayer, struct hsspi_block *blk,
int status)
{
struct coredump_packet *buf =
container_of(blk, struct coredump_packet, blk);
coredump_packet_free(buf);
}
static const struct hsspi_layer_ops coredump_ops = {
.registered = coredump_registered,
.unregistered = coredump_unregistered,
.get = coredump_get,
.received = coredump_received,
.sent = coredump_sent,
};
char *debug_coredump_get(struct debug *dbg, size_t *len)
{
char *data;
struct qm35_ctx *qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
*len = qm35_hdl->coredump_layer.coredump_data_wr_idx;
data = qm35_hdl->coredump_layer.coredump_data;
return data;
}
int debug_coredump_force(struct debug *dbg)
{
struct coredump_packet *p;
struct coredump_common_hdr hdr = { .cmd_id = COREDUMP_FORCE_CMD };
struct qm35_ctx *qm35_hdl;
pr_info("qm35: force coredump");
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
p = coredump_packet_alloc(sizeof(hdr));
if (!p)
return -ENOMEM;
memcpy(p->blk.data, &hdr, sizeof(hdr));
return hsspi_send(&qm35_hdl->hsspi, &qm35_hdl->coredump_layer.hlayer,
&p->blk);
}
static const struct debug_coredump_ops debug_coredump_ops = {
.coredump_get = debug_coredump_get,
.coredump_force = debug_coredump_force,
};
int coredump_layer_init(struct coredump_layer *layer, struct debug *debug)
{
layer->hlayer.name = "QM35 COREDUMP";
layer->hlayer.id = UL_COREDUMP;
layer->hlayer.ops = &coredump_ops;
layer->coredump_data = NULL;
layer->coredump_data_wr_idx = 0;
layer->coredump_size = 0;
layer->coredump_crc = 0;
layer->coredump_status = 0;
timer_setup(&layer->timer, corredump_on_expired_timer, 0);
debug->coredump_ops = &debug_coredump_ops;
return 0;
}
void coredump_layer_deinit(struct coredump_layer *layer)
{
kfree(layer->coredump_data);
}