| /* |
| * Suspend-to-RAM |
| * |
| * Copyright 2006 Pavel Machek <pavel@suse.cz> |
| * 2007 Stefan, Rafael, Tim, Luca |
| * Distribute under GPLv2. |
| */ |
| |
| #include "config.h" |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <dirent.h> |
| #include <unistd.h> |
| |
| #include <pci/pci.h> |
| |
| #include "vbetool/vbetool.h" |
| #include "vt.h" |
| #include "s2ram.h" |
| #include "config_parser.h" |
| #include "whitelist.h" |
| |
| /* From dmidecode.c */ |
| void dmi_scan(void); |
| |
| static void *vbe_buffer; |
| static unsigned char vga_pci_state[256]; |
| static struct pci_dev vga_dev; |
| static struct pci_access *pacc; |
| /* Flags set from whitelist */ |
| static int flags, vbe_mode = -1, dmi_scanned; |
| static int force; |
| static int fb_nosuspend; |
| |
| char bios_version[1024], sys_vendor[1024], sys_product[1024], sys_version[1024]; |
| |
| /* return codes for s2ram_is_supported */ |
| #define S2RAM_OK 0 |
| #define S2RAM_FAIL 1 |
| #define S2RAM_NOFB 126 |
| #define S2RAM_UNKNOWN 127 |
| |
| void identify_machine(void) |
| { |
| if (!dmi_scanned) { |
| dmi_scan(); |
| dmi_scanned = 1; |
| } |
| |
| printf("This machine can be identified by:\n"); |
| printf(" sys_vendor = \"%s\"\n" |
| " sys_product = \"%s\"\n" |
| " sys_version = \"%s\"\n" |
| " bios_version = \"%s\"\n", |
| sys_vendor, sys_product, sys_version, bios_version); |
| printf("See http://suspend.sf.net/s2ram-support.html for details.\n" |
| "\n" |
| "If you report a problem, please include the complete output above.\n" |
| "If you report success, please make sure you tested it from both X and\n" |
| "the text console and preferably without proprietary drivers.\n"); |
| } |
| |
| static int set_acpi_video_mode(int mode) |
| { |
| unsigned long acpi_video_flags; |
| FILE *f = fopen("/proc/sys/kernel/acpi_video_flags", "r"); |
| if (!f) { |
| printf("/proc/sys/kernel/acpi_video_flags does not exist; you need a kernel >=2.6.16.\n"); |
| return S2RAM_FAIL; |
| } |
| /* read the old setting from /proc */ |
| if (fscanf(f, "%ld", &acpi_video_flags) != 1) { |
| printf("/proc/sys/kernel/acpi_video_flags format is invalid\n"); |
| return S2RAM_FAIL; |
| } |
| /* rewind() seems not to work on /proc files, so close and reopen it */ |
| fclose(f); |
| f = fopen("/proc/sys/kernel/acpi_video_flags", "w"); |
| if (!f) { |
| printf("cannot open /proc/sys/kernel/acpi_video_flags " |
| "for writing (not running as root?)\n"); |
| return S2RAM_FAIL; |
| } |
| /* mask out bits 0 and 1 */ |
| acpi_video_flags = acpi_video_flags & (~0UL - S3_BIOS - S3_MODE); |
| fprintf(f, "%ld", acpi_video_flags | mode); |
| fflush(f); |
| fclose(f); |
| return S2RAM_OK; |
| } |
| |
| static int match(const char *t, const char *s) |
| { |
| int len = strlen(s); |
| /* empty string matches always */ |
| if (len == 0) |
| return 1; |
| |
| if (s[len-1] == '*') { |
| len--; |
| return !strncmp(t, s, len); |
| } else { |
| return !strcmp(t,s); |
| } |
| } |
| |
| static int machine_match(void) |
| { |
| if (!dmi_scanned) { |
| dmi_scan(); |
| dmi_scanned = 1; |
| } |
| |
| int i; |
| /* sys_vendor = NULL terminates the whitelist array */ |
| for (i = 0; whitelist[i].sys_vendor; i++) { |
| if (match(sys_vendor, whitelist[i].sys_vendor) && |
| match(sys_product, whitelist[i].sys_product) && |
| match(sys_version, whitelist[i].sys_version) && |
| match(bios_version, whitelist[i].bios_version)) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| static void fbcon_state(int state) |
| { |
| DIR *d; |
| FILE *f; |
| struct dirent *entry; |
| char statefile[255]; |
| |
| if ((d = opendir("/sys/class/graphics")) == NULL) |
| return; |
| while ((entry = readdir(d)) != NULL) { |
| if (entry->d_name[0] == '.') |
| continue; |
| snprintf(statefile, 255, "/sys/class/graphics/%s/state", entry->d_name); |
| if (!access(statefile, W_OK)) { |
| printf("fbcon %s state %d\n", entry->d_name, state); |
| f = fopen(statefile, "w"); |
| if (!f) { |
| printf("s2ram: cannot write to %s\n", statefile); |
| continue; |
| } |
| fprintf(f, "%d", state); |
| fclose(f); |
| } |
| } |
| } |
| |
| static void suspend_fbcon(void) |
| { |
| fbcon_state(1); |
| } |
| |
| static void resume_fbcon(void) |
| { |
| fbcon_state(0); |
| } |
| |
| int s2ram_check(int id) |
| { |
| int ret = S2RAM_OK; |
| |
| if (id < 0) { |
| ret = S2RAM_UNKNOWN; |
| } else { |
| flags = whitelist[id].flags; |
| if ((flags & NOFB) && is_framebuffer()) |
| ret = S2RAM_NOFB; |
| if (flags & UNSURE) |
| printf("ATTENTION:\nYour machine is in the whitelist " |
| " but the entry has not been confirmed.\n" |
| "Please try to find the best options and " |
| "report them as explained on\n" |
| "http://suspend.sf.net/s2ram-support.html.\n\n"); |
| } |
| |
| return ret; |
| } |
| |
| int machine_known(void) |
| { |
| int i = machine_match(); |
| if (i < 0) { |
| printf("Machine unknown\n"); |
| identify_machine(); |
| return 1; |
| } |
| |
| s2ram_check(i); |
| |
| printf("Machine matched entry %d:\n" |
| " sys_vendor = '%s'\n" |
| " sys_product = '%s'\n" |
| " sys_version = '%s'\n" |
| " bios_version = '%s'\n", i, |
| whitelist[i].sys_vendor, whitelist[i].sys_product, |
| whitelist[i].sys_version, whitelist[i].bios_version); |
| printf("Fixes: 0x%x %s%s%s%s%s%s%s%s%s\n", flags, |
| (flags & VBE_SAVE) ? "VBE_SAVE " : "", |
| (flags & VBE_POST) ? "VBE_POST " : "", |
| (flags & VBE_MODE) ? "VBE_MODE " : "", |
| (flags & RADEON_OFF) ? "RADEON_OFF " : "", |
| (flags & S3_BIOS) ? "S3_BIOS " : "", |
| (flags & S3_MODE) ? "S3_MODE " : "", |
| (flags & NOFB) ? "NOFB " : "", |
| (flags & PCI_SAVE) ? "PCI_SAVE " : "", |
| (flags & UNSURE) ? "UNSURE " : ""); |
| /* in case of a bugreport we might need to find a better match than |
| * the one we already have (additional BIOS version e.g)... |
| */ |
| identify_machine(); |
| return (flags & UNSURE); |
| } |
| |
| static int find_vga(void) |
| { |
| struct pci_dev *dev; |
| unsigned int class; |
| |
| pci_scan_bus(pacc); /* We want to get the list of devices */ |
| |
| for (dev=pacc->devices; dev; dev=dev->next) { |
| pci_fill_info(dev, PCI_FILL_IDENT); |
| class = pci_read_word(dev, PCI_CLASS_DEVICE); |
| if (class == 0x300) |
| break; |
| } |
| |
| if (!dev) |
| return 0; |
| |
| memcpy(&vga_dev, dev, sizeof(*dev)); |
| vga_dev.next = NULL; |
| |
| return 1; |
| } |
| |
| static void save_vga_pci(void) |
| { |
| pci_read_block(&vga_dev, 0, vga_pci_state, 256); |
| } |
| |
| static void restore_vga_pci(void) |
| { |
| pci_write_block(&vga_dev, 0, vga_pci_state, 256); |
| } |
| |
| /* warning: we have to be on a text console when calling this */ |
| int s2ram_hacks(void) |
| { |
| int ret = 0; |
| |
| ret = set_acpi_video_mode(flags & (S3_BIOS | S3_MODE)); |
| |
| if (ret) |
| return ret; |
| |
| if (flags & VBE_SAVE) { |
| int size; |
| vbetool_init(); |
| printf("Calling save_state\n"); |
| vbe_buffer = __save_state(&size); |
| } |
| if (flags & VBE_MODE) { |
| vbetool_init(); |
| printf("Calling get_mode\n"); |
| vbe_mode = __get_mode(); |
| } |
| if (flags & RADEON_OFF) { |
| map_radeon_cntl_mem(); |
| printf("Calling radeon_cmd_light(0)\n"); |
| radeon_cmd_light(0); |
| } |
| if (flags & PCI_SAVE) { |
| pacc = pci_alloc(); /* Get the pci_access structure */ |
| pci_init(pacc); /* Initialize the PCI library */ |
| |
| if (find_vga()) { |
| printf("saving PCI config of device %02x:%02x.%d\n", |
| vga_dev.bus, vga_dev.dev, vga_dev.func); |
| save_vga_pci(); |
| } else |
| /* pci_save requested, no VGA device found => abort */ |
| return 1; |
| } |
| if (fb_nosuspend) |
| printf("ATTENTION: --nofbsuspend is a debugging tool only.\n" |
| "\tIf your machine needs this to work, please report " |
| "this as a bug.\n"); |
| else |
| suspend_fbcon(); |
| |
| return 0; |
| } |
| |
| int s2ram_is_supported(void) |
| { |
| int ret = 0, id; |
| |
| if (flags && !force) { |
| printf("The acpi_sleep, vbe_save, vbe_post, radeontool and " |
| "pci_save parameters must be used with --force\n\n"); |
| return EINVAL; |
| } |
| |
| if (!force) { |
| id = machine_match(); |
| ret = s2ram_check(id); |
| } |
| |
| return ret; |
| } |
| |
| /* Actually enter the suspend. May be ran on frozen system. */ |
| int s2ram_do(void) |
| { |
| return s2ram_generic_do(); |
| } |
| |
| void s2ram_resume(void) |
| { |
| if (flags & PCI_SAVE) { |
| printf("restoring PCI config of device %02x:%02x.%d\n", |
| vga_dev.bus, vga_dev.dev, vga_dev.func); |
| restore_vga_pci(); |
| |
| pci_cleanup(pacc); |
| } |
| // FIXME: can we call vbetool_init() multiple times without cleaning up? |
| if (flags & VBE_POST) { |
| vbetool_init(); |
| printf("Calling do_post\n"); |
| do_post(); |
| } |
| if (vbe_buffer) { |
| vbetool_init(); |
| printf("Calling restore_state_from\n"); |
| restore_state_from(vbe_buffer); |
| } |
| if (vbe_mode >= 0) { |
| vbetool_init(); |
| printf("Calling set_vbe_mode\n"); |
| do_set_mode(vbe_mode, 0); |
| } |
| if (!fb_nosuspend) |
| resume_fbcon(); |
| if (flags & RADEON_OFF) { |
| printf("Calling radeon_cmd_light(1)\n"); |
| radeon_cmd_light(1); |
| } |
| } |
| |
| void s2ram_add_flag(int opt, const char *opt_arg) |
| { |
| /* The characters are the `deprecated' short options. They will not |
| * clash with the new labels untill we reach quirk 65... */ |
| switch (opt) { |
| case 1: |
| case 'f': |
| force = 1; |
| break; |
| case 2: |
| case 's': |
| flags |= VBE_SAVE; |
| break; |
| case 3: |
| case 'p': |
| flags |= VBE_POST; |
| break; |
| case 4: |
| case 'm': |
| flags |= VBE_MODE; |
| break; |
| case 5: |
| case 'r': |
| flags |= RADEON_OFF; |
| break; |
| case 6: |
| case 'v': |
| flags |= PCI_SAVE; |
| break; |
| case 7: |
| case 'a': |
| flags |= (atoi(opt_arg) & (S3_BIOS | S3_MODE)); |
| break; |
| case 8: |
| fb_nosuspend = 1; |
| break; |
| } |
| } |