blob: 7b24591ed8d9bfdbef2af9ad1a8eb552d90af5e8 [file] [log] [blame]
/* Copyright (c) 2008-2009, 2012-2013, The Linux Foundation.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed 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
* GNU General Public License for more details.
*
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/ratelimit.h>
#include <linux/crc-ccitt.h>
#include "diagchar_hdlc.h"
#include "diagchar.h"
MODULE_LICENSE("GPL v2");
#define CRC_16_L_SEED 0xFFFF
#define CRC_16_L_STEP(xx_crc, xx_c) \
crc_ccitt_byte(xx_crc, xx_c)
void diag_hdlc_encode(struct diag_send_desc_type *src_desc,
struct diag_hdlc_dest_type *enc)
{
uint8_t *dest;
uint8_t *dest_last;
const uint8_t *src;
const uint8_t *src_last;
uint16_t crc;
unsigned char src_byte = 0;
enum diag_send_state_enum_type state;
unsigned int used = 0;
if (src_desc && enc) {
/* Copy parts to local variables. */
src = src_desc->pkt;
src_last = src_desc->last;
state = src_desc->state;
dest = enc->dest;
dest_last = enc->dest_last;
if (state == DIAG_STATE_START) {
crc = CRC_16_L_SEED;
state++;
} else {
/* Get a local copy of the CRC */
crc = enc->crc;
}
/* dest or dest_last may be NULL to trigger a
state transition only */
if (dest && dest_last) {
/* This condition needs to include the possibility
of 2 dest bytes for an escaped byte */
while (src <= src_last && dest <= dest_last) {
src_byte = *src++;
if ((src_byte == CONTROL_CHAR) ||
(src_byte == ESC_CHAR)) {
/* If the escape character is not the
last byte */
if (dest != dest_last) {
crc = CRC_16_L_STEP(crc,
src_byte);
*dest++ = ESC_CHAR;
used++;
*dest++ = src_byte
^ ESC_MASK;
used++;
} else {
src--;
break;
}
} else {
crc = CRC_16_L_STEP(crc, src_byte);
*dest++ = src_byte;
used++;
}
}
if (src > src_last) {
if (state == DIAG_STATE_BUSY) {
if (src_desc->terminate) {
crc = ~crc;
state++;
} else {
/* Done with fragment */
state = DIAG_STATE_COMPLETE;
}
}
while (dest <= dest_last &&
state >= DIAG_STATE_CRC1 &&
state < DIAG_STATE_TERM) {
/* Encode a byte of the CRC next */
src_byte = crc & 0xFF;
if ((src_byte == CONTROL_CHAR)
|| (src_byte == ESC_CHAR)) {
if (dest != dest_last) {
*dest++ = ESC_CHAR;
used++;
*dest++ = src_byte ^
ESC_MASK;
used++;
crc >>= 8;
} else {
break;
}
} else {
crc >>= 8;
*dest++ = src_byte;
used++;
}
state++;
}
if (state == DIAG_STATE_TERM) {
if (dest_last >= dest) {
*dest++ = CONTROL_CHAR;
used++;
state++; /* Complete */
}
}
}
}
/* Copy local variables back into the encode structure. */
enc->dest = dest;
enc->dest_last = dest_last;
enc->crc = crc;
src_desc->pkt = src;
src_desc->last = src_last;
src_desc->state = state;
}
return;
}
int diag_hdlc_decode(struct diag_hdlc_decode_type *hdlc)
{
uint8_t *src_ptr = NULL, *dest_ptr = NULL;
unsigned int src_length = 0, dest_length = 0;
unsigned int len = 0;
unsigned int i;
uint8_t src_byte;
int pkt_bnd = 0;
int msg_start;
if (hdlc && hdlc->src_ptr && hdlc->dest_ptr &&
(hdlc->src_size - hdlc->src_idx > 0) &&
(hdlc->dest_size - hdlc->dest_idx > 0)) {
msg_start = (hdlc->src_idx == 0) ? 1 : 0;
src_ptr = hdlc->src_ptr;
src_ptr = &src_ptr[hdlc->src_idx];
src_length = hdlc->src_size - hdlc->src_idx;
dest_ptr = hdlc->dest_ptr;
dest_ptr = &dest_ptr[hdlc->dest_idx];
dest_length = hdlc->dest_size - hdlc->dest_idx;
for (i = 0; i < src_length; i++) {
src_byte = src_ptr[i];
if (hdlc->escaping) {
dest_ptr[len++] = src_byte ^ ESC_MASK;
hdlc->escaping = 0;
} else if (src_byte == ESC_CHAR) {
if (i == (src_length - 1)) {
hdlc->escaping = 1;
i++;
break;
} else {
dest_ptr[len++] = src_ptr[++i]
^ ESC_MASK;
}
} else if (src_byte == CONTROL_CHAR) {
dest_ptr[len++] = src_byte;
/*
* If this is the first byte in the message,
* then it is part of the command. Otherwise,
* consider it as the last byte of the
* message.
*/
if (msg_start && i == 0 && src_length > 1)
continue;
i++;
pkt_bnd = 1;
break;
} else {
dest_ptr[len++] = src_byte;
}
if (len >= dest_length) {
i++;
break;
}
}
hdlc->src_idx += i;
hdlc->dest_idx += len;
}
return pkt_bnd;
}
int crc_check(uint8_t *buf, uint16_t len)
{
uint16_t crc = CRC_16_L_SEED;
uint8_t sent_crc[2] = {0, 0};
/*
* The minimum length of a valid incoming packet is 4. 1 byte
* of data and 3 bytes for CRC
*/
if (!buf || len < 4) {
pr_err_ratelimited("diag: In %s, invalid packet or length, buf: 0x%x, len: %d",
__func__, (int)buf, len);
return -EIO;
}
/*
* Run CRC check for the original input. Skip the last 3 CRC
* bytes
*/
crc = crc_ccitt(crc, buf, len-3);
crc ^= CRC_16_L_SEED;
/* Check the computed CRC against the original CRC bytes. */
sent_crc[0] = buf[len-3];
sent_crc[1] = buf[len-2];
if (crc != *((uint16_t *)sent_crc)) {
pr_debug("diag: In %s, crc mismatch. expected: %x, sent %x.\n",
__func__, crc, *((uint16_t *)sent_crc));
return -EIO;
}
return 0;
}