/* * 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 #include #include #include #include #include #include #include #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);