266 lines
6.4 KiB
C

/*
* Copyright (C) 2011 The Chromium OS Authors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#define pr_fmt(fmt) "chromeos_arm: " fmt
#include <linux/bcd.h>
#include <linux/gpio.h>
#include <linux/notifier.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include "../chromeos.h"
#include "elog.h"
struct chromeos_arm_elog_panic_buffer {
uint32_t start;
uint32_t size;
void __iomem *virt_addr;
struct notifier_block nb;
};
/*
* Update the checksum at the last byte
*/
static void elog_update_checksum(struct event_header *event, u8 checksum)
{
u8 *event_data = (u8 *)event;
event_data[event->length - 1] = checksum;
}
/*
* Simple byte checksum for events
*/
static u8 elog_checksum_event(struct event_header *event)
{
u8 index, checksum = 0;
u8 *data = (u8 *)event;
for (index = 0; index < event->length; index++)
checksum += data[index];
return checksum;
}
/*
* Populate timestamp in event header with current time
*/
static void elog_fill_timestamp(struct event_header *event)
{
struct timeval timeval;
struct tm time;
do_gettimeofday(&timeval);
time_to_tm(timeval.tv_sec, 0, &time);
event->second = bin2bcd(time.tm_sec);
event->minute = bin2bcd(time.tm_min);
event->hour = bin2bcd(time.tm_hour);
event->day = bin2bcd(time.tm_mday);
event->month = bin2bcd(time.tm_mon + 1);
event->year = bin2bcd(time.tm_year % 100);
}
/*
* Fill out an event structure with space for the data and checksum.
*/
void elog_prepare_event(struct event_header *event, u8 event_type, void *data,
u8 data_size)
{
event->type = event_type;
event->length = sizeof(*event) + data_size + 1;
elog_fill_timestamp(event);
if (data_size)
memcpy(&event[1], data, data_size);
/* Zero the checksum byte and then compute checksum */
elog_update_checksum(event, 0);
elog_update_checksum(event, -(elog_checksum_event(event)));
}
static int chromeos_arm_elog_panic(struct notifier_block *this,
unsigned long p_event, void *ptr)
{
struct chromeos_arm_elog_panic_buffer *buf;
uint32_t reason = ELOG_SHUTDOWN_PANIC;
const u8 data_size = sizeof(reason);
union {
struct event_header hdr;
u8 bytes[sizeof(struct event_header) + data_size + 1];
} event;
buf = container_of(this, struct chromeos_arm_elog_panic_buffer, nb);
elog_prepare_event(&event.hdr, ELOG_TYPE_OS_EVENT, &reason, data_size);
memcpy_toio(buf->virt_addr, event.bytes, sizeof(event.bytes));
return NOTIFY_DONE;
}
static int chromeos_arm_panic_init(struct platform_device *pdev, u32 start,
u32 size)
{
int ret = -EINVAL;
struct chromeos_arm_elog_panic_buffer *buf;
buf = kmalloc(sizeof(*buf), GFP_KERNEL);
if (!buf) {
dev_err(&pdev->dev, "failed to allocate panic notifier.\n");
ret = -ENOMEM;
goto fail1;
}
buf->start = start;
buf->size = size;
buf->nb.notifier_call = chromeos_arm_elog_panic;
if (!request_mem_region(start, size, "elog panic event")) {
dev_err(&pdev->dev, "failed to request panic event buffer.\n");
goto fail2;
}
buf->virt_addr = ioremap(start, size);
if (!buf->virt_addr) {
dev_err(&pdev->dev, "failed to map panic event buffer.\n");
goto fail3;
}
atomic_notifier_chain_register(&panic_notifier_list, &buf->nb);
platform_set_drvdata(pdev, buf);
return 0;
fail3:
release_mem_region(start, size);
fail2:
kfree(buf);
fail1:
return ret;
}
static int chromeos_arm_probe(struct platform_device *pdev)
{
int gpio, err, active_low;
enum of_gpio_flags flags;
u32 elog_panic_event[2];
struct device_node *np = pdev->dev.of_node;
if (!np) {
err = -ENODEV;
goto err;
}
gpio = of_get_named_gpio_flags(np, "write-protect-gpio", 0, &flags);
if (!gpio_is_valid(gpio)) {
dev_err(&pdev->dev, "invalid write-protect gpio descriptor\n");
err = -EINVAL;
goto err;
}
active_low = !!(flags & OF_GPIO_ACTIVE_LOW);
err = gpio_request_one(gpio, GPIOF_DIR_IN, "firmware-write-protect");
if (err)
goto err;
err = gpio_sysfs_set_active_low(gpio, active_low);
if (err)
goto err;
gpio_export(gpio, 0);
gpio_export_link(&pdev->dev, "write-protect", gpio);
gpio = of_get_named_gpio_flags(np, "recovery-gpio", 0, &flags);
if (!gpio_is_valid(gpio)) {
dev_err(&pdev->dev, "invalid recovery gpio descriptor\n");
err = -EINVAL;
goto err;
}
active_low = !!(flags & OF_GPIO_ACTIVE_LOW);
err = gpio_request_one(gpio, GPIOF_DIR_IN, "firmware-recovery");
if (err)
goto err;
err = gpio_sysfs_set_active_low(gpio, active_low);
if (err)
goto err;
gpio_export(gpio, 0);
gpio_export_link(&pdev->dev, "recovery", gpio);
if (!of_property_read_u32_array(np, "elog-panic-event",
elog_panic_event,
ARRAY_SIZE(elog_panic_event))) {
err = chromeos_arm_panic_init(pdev, elog_panic_event[0],
elog_panic_event[1]);
if (err)
goto err;
}
dev_info(&pdev->dev, "chromeos system detected\n");
err = 0;
err:
of_node_put(np);
return err;
}
static int chromeos_arm_remove(struct platform_device *pdev)
{
struct chromeos_arm_elog_panic_buffer *buf;
buf = platform_get_drvdata(pdev);
platform_set_drvdata(pdev, NULL);
if (buf) {
atomic_notifier_chain_unregister(&panic_notifier_list,
&buf->nb);
release_mem_region(buf->start, buf->size);
iounmap(buf->virt_addr);
kfree(buf);
}
return 0;
}
static struct platform_driver chromeos_arm_driver = {
.probe = chromeos_arm_probe,
.remove = chromeos_arm_remove,
.driver = {
.name = "chromeos_arm",
},
};
static int __init chromeos_arm_init(void)
{
struct device_node *fw_dn;
struct platform_device *pdev;
fw_dn = of_find_compatible_node(NULL, NULL, "chromeos-firmware");
if (!fw_dn)
return -ENODEV;
pdev = platform_device_register_simple("chromeos_arm", -1, NULL, 0);
pdev->dev.of_node = fw_dn;
platform_driver_register(&chromeos_arm_driver);
return 0;
}
subsys_initcall(chromeos_arm_init);