// SPDX-License-Identifier: GPL-2.0-only /* * Core driver for the microcontroller unit in QNAP NAS devices that is * connected via a dedicated UART port. * * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
*/
/* The longest command found so far is 5 bytes long */ #define QNAP_MCU_MAX_CMD_SIZE 5 #define QNAP_MCU_MAX_DATA_SIZE 36 #define QNAP_MCU_CHECKSUM_SIZE 1
/** * struct qnap_mcu_reply - Reply to a command * * @data: Buffer to store reply payload in * @length: Expected reply length, including the checksum * @received: Received number of bytes, so far * @done: Triggered when the entire reply has been received
*/ struct qnap_mcu_reply {
u8 *data;
size_t length;
size_t received; struct completion done;
};
/** * struct qnap_mcu - QNAP NAS embedded controller * * @serdev: Pointer to underlying serdev * @bus_lock: Lock to serialize access to the device * @reply: Reply data structure * @variant: Device variant specific information * @version: MCU firmware version
*/ struct qnap_mcu { struct serdev_device *serdev; struct mutex bus_lock; struct qnap_mcu_reply reply; conststruct qnap_mcu_variant *variant;
u8 version[QNAP_MCU_VERSION_LEN];
};
/* * The QNAP-MCU uses a basic XOR checksum. * It is always the last byte and XORs the whole previous message.
*/ static u8 qnap_mcu_csum(const u8 *buf, size_t size)
{
u8 csum = 0;
if (!reply->length) {
dev_warn(dev, "Received %zu bytes, we were not waiting for\n", size); return size;
}
while (src < end) {
reply->data[reply->received] = *src++;
reply->received++;
if (reply->received == reply->length) { /* We don't expect any characters from the device now */
reply->length = 0;
complete(&reply->done);
/* * We report the consumed number of bytes. If there * are still bytes remaining (though there shouldn't) * the serdev layer will re-execute this handler with * the remainder of the Rx bytes.
*/ return src - buf;
}
}
/* * The only way to get out of the above loop and end up here * is through consuming all of the supplied data, so here we * report that we processed it all.
*/ return size;
}
/* Reply is the 2 command-bytes + 4 bytes describing the version */
ret = qnap_mcu_exec(mcu, cmd, sizeof(cmd), rx, QNAP_MCU_VERSION_LEN + 2); if (ret) return ret;
/* * The MCU controls power to the peripherals but not the CPU. * * So using the PMIC to power off the system keeps the MCU and hard-drives * running. This also then prevents the system from turning back on until * the MCU is turned off by unplugging the power cable. * Turning off the MCU alone on the other hand turns off the hard drives, * LEDs, etc while the main SoC stays running - including its network ports.
*/ staticint qnap_mcu_power_off(struct sys_off_data *data)
{ const u8 cmd[] = { '@', 'C', '0' }; struct qnap_mcu *mcu = data->cb_data; int ret;
ret = qnap_mcu_exec_with_ack(mcu, cmd, sizeof(cmd)); if (ret) {
dev_err(&mcu->serdev->dev, "MCU poweroff failed %d\n", ret); return NOTIFY_STOP;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.