#!/bin/env python # -*- coding: utf-8 -*- # Program to find devices to support in LineageOS # Copyright (C) 2019 Denis 'GNUtoo' Carikli # # 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 3 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, see . import os import re import sys import yaml basedir = '_data' + os.sep + 'devices' results = {} last_lineageos_version = 17.1 def still_supported(document): if not last_lineageos_version in document['versions']: return False if 'channels' in document.keys() and 'discontinued' in document['channels']: return False if 'maintainers' in document.keys() and len(document['maintainers']) == 0: return False return True # In some cases it might be interesting to look if a given device has a modem in # the case where the SOC is known not to have any modem, so we can automatically # know that the is no shared memory between the modem and the device if there is # no modem. # Another case would be for SOCs that have optional modems where the device has # no modem feature and that we are sure that the modem CPU is not running any code. def device_has_modem(vendor, product): if vendor == 'Google' and product == 'Pixel C': return False # unknown return None def device_has_shared_memory_between_modem_and_soc(vendor, product): if vendor == 'Samsung' and product == 'Galaxy S5 Neo': # In the "Samsung Mobile Modem Driver (SVNET2) V1 for # Memory-type Interface" section in lineageos_s5neolte_defconfig # in the lineage-17.0 branch of # https://github.com/LineageOS/android_kernel_samsung_universal7580 # there is the following configuration: # CONFIG_LINK_DEVICE_SHMEM=y # CONFIG_LINK_DEVICE_HSIC is not set return True if vendor == 'Samsung' and product == 'Galaxy J7 (2015)': # lineageos_j7elte_defconfig in the lineage-17.1 branch of # android_kernel_samsung_universal7580 has the following: # CONFIG_LINK_DEVICE_SHMEM=y. All other CONFIG_LINK_DEVICE_ # are 'is not set'. return True # unknown return None def soc_has_modem(vendor, product): if vendor == 'HiSilicon': pass elif vendor == 'Intel': pass elif vendor in ['Nvidia', 'NVIDIA'] and re.search("^Tegra 4", product): # The modem could be implemented in software pass elif vendor in ['Nvidia', 'NVIDIA'] and re.search("^Tegra K1", product): pass elif vendor in ['Nvidia', 'NVIDIA'] and re.search("^Tegra X1", product): pass elif vendor == 'Qualcomm' and re.search("^MSM", product): return True elif vendor == 'Qualcomm' and re.search("^APQ", product) or re.search("^Snapdragon 600", product): return False elif vendor == 'Qualcomm' and product == "Qualcomm SDM845 Snapdragon 845": return True elif vendor == 'Qualcomm' and re.search("^SDM", product): pass elif vendor == 'Qualcomm' and (re.search("^SM", product) or re.search("^sm", product)): pass elif vendor == 'Qualcomm' and re.search("^Snapdragon", product): pass elif vendor == 'Samsung' and re.search("^Exynos", product): # Some exynos modems do exists but they are either standalone # modems or are SOCs where the AP is a Cortex M7 which are very # unlikely to be able to run Android for smartphones or tablets return False elif vendor == 'TI' and product in ['OMAP4430', 'OMAP4460']: # The modem could be implemented in software in the DSP but I never saw # it on any device. It's used like that on the SysmoBTS which are BTS, # not phones or a tablets. return False else: print("<{}|{}>".format(vendor, product)) sys.exit(1) # Unknown return None def parse_soc(soc): soc_vendors = [ "HiSilicon", "Intel", "NVIDIA", "Nvidia", "Qualcomm", "Samsung", "TI"] soc_vendors_quirks = { # ^Match : Vendor 'Exynos' : 'Samsung', } results = {} found = False for vendor in soc_vendors: match = re.search("^{0} (.*)".format(vendor), soc) if match: found = True if vendor == 'NVIDIA': vendor = 'Nvidia' results['vendor'] = vendor results['product'] = match.group(1) results['has_modem'] = soc_has_modem(results['vendor'], results['product']) if not found: for match_data, vendor in soc_vendors_quirks.items(): match = re.search("^{0} (.*)".format(match_data), soc) if match: found = True results['vendor'] = vendor results['product'] = soc results['has_modem'] = soc_has_modem(results['vendor'], results['product']) if not 'vendor' in results: print("TODO: Handle vendor in \"{}\"".format(soc)) sys.exit(1) return results def battery_is_removable(battery): # Example: Set top box if battery == "None" or battery == None: return None if 'removable' not in battery: print("TODO: Handle batteries where removable is absent: {}".format(battery)) sys.exit(1) removable_battery = battery.get('removable') if removable_battery == True: return True elif removable_battery == False: return False else: print("TODO: Handle batteries where removable is \"{}\": {}".format(removable, battery)) sys.exit(1) def has_removable_battery(document): removable_battery = None if 'battery' not in document: # We don't know the policies reguarding incomplete data so the data # may either be incomplete or the device may not have a battery. print("TODO: Add support for devices lacking a battery") sys.exit(1) else: battery = document.get('battery') if type(battery) is not list: return battery_is_removable(battery) else: for battery_version in battery: if battery_version == None: continue if len(battery_version.keys()) != 1: print("TODO: Add support for battery versions with multiple keys: {}".format(battery_version)) sys.exit(1) nr_removable = 0 nr_not_removable = 0 (battery_name, battery_data), = battery_version.items() removable = battery_is_removable(battery_data) if removable == True: nr_removable += 1 elif removable == False: nr_not_removable += 1 if nr_removable == 0 and nr_not_removable > 0: return False elif nr_not_removable == 0 and nr_removable > 0: return True else: print("TODO: The data has removable and non-removable batteries" "versions for the same device: {}".format(battery)) print(" Add support for it in the parsing code") sys.exit(1) def interesting_for_replicant(document): # For smartphones or tablets with a modem, since the modem is in # the same SOC, we would need to do some extensive review of the SOC # architecture to understand if the modem can be isolated with an IOMMU # We would also need to make sure that the IOMMU is correctly setup (hard) # and that the setup happens before the modem core are booted. # For devices without a modem, we would still need to make sure that the # sound card and the microphone is completely under the control of free # software and that there aren't too much proprietary libraries to replace soc = parse_soc(document['soc']) if soc_has_modem(soc['vendor'], soc['product']) == True: return False if device_has_shared_memory_between_modem_and_soc(document['vendor'], document['name']): return False # Non replaceable batteries causes too much issues for both users # and developers as once you buy a device second hand, the battery # doesn't last as much as when the device is new. On some devices, # replicing non-removable batteries can be very difficult and damage # the device along the way # # There are some devices where the battery is not removable but # since the device can be easily opened, and that the battery has # a connector, it might be possible for an experienced user or # developer, or a repair shop, or a repair café to open the device # and replace the battery. # # However it would take more research to understand if compatible # batteries replacement can easily be found. The LineageOS wiki also # doesn't have a way to distinguish between devices that can be easily # opened and the ones that aren't. # # So for now we aproximate non-removable batteries to non-replaceable # batteries until we find a way to deal with it. if document['type'] != 'Set top box' and not has_removable_battery(document): return False return True def store_infos(results, document): fields = ['vendor', 'name', 'type'] device_dict = {} for field in fields: device_dict[field] = document[field] device_dict['removable_battery'] = has_removable_battery(document) soc = document['soc'] soc_data = parse_soc(soc) soc_vendor = None soc_product = None if soc_data['vendor'] not in results: results[soc_data['vendor']] = {} soc_vendor = results[soc_data['vendor']] if soc_data['product'] not in soc_vendor: soc_vendor[soc_data['product']] = [] soc_product = soc_vendor[soc_data['product']] soc_product.append(device_dict) def print_results(results): soc_vendors = list(results.keys()) soc_vendors.sort() for soc_vendor in soc_vendors: print ("{0}:".format(soc_vendor)) for soc_product in results[soc_vendor]: print ("- {0}:".format(soc_product)) for device in results[soc_vendor][soc_product]: print(" * {0}: {1} ({2})".format( device['vendor'], device['name'], device['type'])) def find_devices(path): for filename in os.listdir(path + os.sep + basedir): filepath = path + os.sep + basedir + os.sep + filename if re.search("\.yml$", filepath): yaml_file = open(filepath, 'r') document = yaml.safe_load(yaml_file) if still_supported(document) and interesting_for_replicant(document): store_infos(results, document) print_results(results) def usage(argv0): progname = os.path.basename(argv0) print("{} [path/to/lineage_wiki]".format(progname)) sys.exit(1) if len(sys.argv) != 2: usage(sys.argv[0]) find_devices(sys.argv[1])