精华内容
下载资源
问答
  • 财险产保险公司应用系统各子系统简介 原创 ERP 作者:chenda2001 时间:2009-07-30 20:31:12 6597 0 前几天在论坛中发表了一个“财产核心业务系统”简介的帖子。这里在自己blog上也发布下。     系统...

    财险产保险公司应用系统各子系统简介

    原创 ERP 作者:chenda2001 时间:2009-07-30 20:31:12 6597 0

    前几天在论坛中发表了一个“财产核心业务系统”简介的帖子。这里在自己blog上也发布下。

     

     

    系统如下:

    1) 核心业务系统

    2) 双核系统(核保与核赔)

    3) 理赔系统

    4) 呼叫中心系统

    5) 客户服务管理系统

    6) 规则引擎系统

    7) 人力资源系统

    8) 财务系统

    9) 保监会对接系统

    10) 保监会稽核系统

    11) 准备金系统

    12) 再保系统 

    13) 收付费系统

    14) 单证管理系统

    15) 反洗钱系统

    16) 事件管理系统

    17) 需求管理系统

    18) 后台提数程序

    19) 客户信息系统

    20) 行业渠道系统

    21) 车型系统

    22) 电子商务网站

    23) 零配件系统

    24) 车辆定核损系统

    25) 销售管理系统

    26) Gps查勘定损系统

    27) 车险行业信息平台系统

    28) OA

    29) 短信平台

    30) 电销系统

    31) 矩阵决策支持系统

    32) 商业智能系统

    33) CRM系统

    介绍下核心系统

    这里所说的核心系统主要是指承保系统。

    根据险种不同,一般分为车险系统、非车险系统、意外险系统。

    说明:最初以前一个核心系统是全险种处理,现在随着保险公司内部管理的变化,系统压力的增大,各种服务实施等,都被拆分成独立的子系统。

    从系统功能上说,一般分为投保、承保、批改等(实际上对应于业务流程)

    投保功能,通常所说的就是新保录入,一般根据保险条款,可以分为投保人信息、被保险人信息、承保标的信息、保险起期、代理人信息,车船税信息、投保单基本信息等。一般系统界面对于不同的产品,展现也有所不一样。出单员在录入完基本信息后就可以提交核保操作。

     

    承保功能,一般包括保单补录、保单查询、保单打印等操作。

    保单补录,简单说就是已经给客户的保单,在事后录入到系统。目前车险在去年陆续上见费出单后,基本上没有补录这功能。非车险还存在。

    保单查询,就是一些保单信息显示。

    保单打印,通常分为套打和全打。简单点说套打,是打印的纸张是已经画好格式的,如交强险保单。全打,每家公司可能处理不一样,有的可能是直接白纸打印,有的是可能是有公司抬头,蓝色底统一印制的纸张。

    批改,相对于售险的保险,一般分为文批和金额。金额就是有保费变化的批改,如批增险种,变更车损险保额。文批通常所说的是变更保险人姓名或地址,换牌等。

     

    昨天比较晚,今天继续大致说下各系统。

    双核系统,全称为核保核赔系统,虽说系统比较小,但其作用地位不容小觑,是保险公司控制承保风险和管理措施的一道铁闸。举个例子,比如投保家庭自用客车(0-6),商业险总保额<70万,走一核核保,在70至160万之间走二级核保,160W至250W之间走三级核保,在250W以上则走四级核保。从大功能来说,比较明确,处理核保和核赔任务,但系统一些关键数据的生成基本上在核保系统中,不细述啦。

     

    理赔系统。

    从业务流程上来说,包括报案、调度、查勘、定损、核损、核价、单证收集、理算、结案等。现在的理赔系统一般分为车险系统和非车险、意外险系统。车险系统相对来说现在比较成熟,基本上已经能和正常业务部门流程对口上,非车险和意外险基本上还是记录理赔数据作用。对于车险理赔系统,还得提下影像系统,现在基本上每家公司的车险理赔系统要能处理各种车辆事故照片、费用票据、保险单身份证等凭证。

    理赔系统随着保险公司业务的发展,市场的进步,从里面拆分出来的系统也比较多。下面再讲,先上班干活啦。

    [@more@]

    呼叫中心系统

    呼叫中心系统,从狭义上来说就是热线电话,只不过现在此基础上衍生出很多业务功能。一般都有呼入和呼出业务,和寿险估计也差不多。从服务平台架构建立一般包括交换机、IVR服务器、CTI服务器、录音服务器、报表/监控服务器。一般坐席软件基本上也会包括就绪、小休、拨号、接号、保持、咨询、三方通话、监控、强拆、会议等

     

    客户服务管理系统

    客服系统,这个名称有点广,这里所指系统主要针对坐席的,一般包括报案受理、投诉处理、知识库查询、回访处理、录音文件查询、话务质检管理等。报案受理一般理赔系统的开始,指被保险人出险后,及时向保险人进行报案登记。投诉处理一般包括投诉件和抱怨件处理,针对具体的投诉件,会再分业务类型流转到不同的业务部门。知识库查询主要是保险公司内部整理的常用资料供座席查询调用,如各机构理赔服务电话,保险条款查询,新产口咨询等。回访处理,是基于客户服务的前提下,对客户展开的电话及其他形式关怀性质的沟通联系。从实务角度也可以分为承保端和理赔端,承保端的如新签约客户回访、续保客户访等,理赔端的如理赔赔款支付速度、查勘及时率、工作人员的服务态度等。录音文件查询,就是事后对录音文件进行查听。话务质检,主要是根据制定的指标,对坐席话务质量进行检查考核。

     

     

    规则引擎系统

    规则引擎没有深入过,只接触过接口。主要作用,把之前的核保核赔因子现在建立成业务对象模型,一般会分为基本信息、车辆信息、承保险别信息、交强险信息、车型库信息、投保人信息、被保人信息等。然后再根据建立好的规则库进行匹配,最后返回核保级别和核保原因。

     

    人力资源系统

    人务资源系统接触也较少,使用对象基本上是HR部门,一般包括人员管理、合同管理、薪资管理、保险福利、考勤管理、招聘管理、培训管理、绩效考核、规章制度、综合查询、通知(新闻)、员工自助等。用的比较多是人员管理、合同管理、薪资管理等。像薪资管理,一般包括工资套管理、工资变动管理、工资计算与发放、月末处理和查询统计等。工资套管理是该模块的工作基础,需要为不同的人员指定使用的工资套、公式设计、币种设置等功能。人员信息这块估计是中国特色比较明显的,可能包括职务名称、社会保险号、学历( 一般高学历都是有加薪的)、是否有学位、毕业学校、专业、本人联系电话、是否本地户口、职务、籍贯、年龄、职称、紧急联系人、政治面貌等。感慨下,在上海和北京这种城市,如果有本地户口和外来户口,享受的保险福利待遇还是有差别的,什么时候能消除这种户口上种种利益,估计我们整个国家才能成为一个大家庭。

     

    财务系统

    现在财务系统市场上基本上被SAP Oracle用友金蝶等软件公司瓜分,因为财务系统不像业务,经常受市场、管理层监管变化而变化,相对来说比较稳定。财务这块接触比较少,不细述啦。

     

     

    保监会对接系统

     

     

    保监会对接系统比较关键,因为是保监会要的数据,政治意义大些。主要是业务数据、财务数据、再保数据和保单年度数据的提取和统计功能,并且生成向保监会上报的数据文件。主要功能是科目管理、机构管理和数据准备等。一般从采集的中间库数据中,解析出保监会下发的每个指标的基层单位数据值,然后对基层单位数据进行汇总。对汇总的数据进行校验,校验必须满足保监会下发的系统对接标准中的总分关系,即总公司数据等于分公司汇总数,分公司数据等于基层单位汇总数。校验通过后,根据保送频度和报表类型,就可以生成上报的XML格式的报表文件,包括常规报(月/季/半年/年度报)和快保,下载此文件就可以上报数据。

     

     

    保监会稽核系统

    稽核系统也比较关键,也是保监会要的数据,属于对外的系统。所谓稽核,就是稽查和审核的意思,要是哪家公司有幸中奖,那也有得受的。毕竟明细数据和保监会报送的汇总数据可能会存在误差,有误差,就得解释,查数据。一共分为业务表22张,财务表3张,单证表2张,再保险合约2张。具体模型表结构保监会下发的材料中都有。

     

    准备金系统

    准备金是保险公司针对其承担保险业务引起的已有或未来负债而建立的基金,因为其精算评估对保险公司的财务状态和整体经营运作起到举足轻重的作用,一般比较受高层、股东和保监会等的关注。当然该系统也属于监管部门要求,重要性不言而喻,不过有些公司是精算部是自己处理的。准备金分类一般有未到期保费准备金、赔款准备金、长期责任准备金、平衡准备金等。具体精算评估方式比较复杂,自己也没有理解好。

     

    再保系统

    再保系统,个人以为是保险系统里面专业性比较强的系统。理解有限,就不多说,只说明部分概念。再保险(Reinsurance)也称分保,是对保险人所承担的风险责任的保险。再保险是保险的一种,由保险派生发展而来,保险是再保险的基础和前提,而再保险是保险的后盾和保障。再保险的合同当事人为保险人和再保险人,保险标的为分出公司的责任。按照再保险的安排方式分为a、临时再保险(Facultative Reinsurance),解释为根据业务需要,临时选择分保接受人,经协商达成协议,逐笔成交。b、合同再保险(Treaty Reinsurance),定义为由保险人与再保险人用签定合同的方式确定双方的再保险关系,在一定时期内对一宗或一类业务,进行缔约人之间的约束性的再保险。c、预约再保险,介于合同和临时分保之间的一种分保方式。按责任限制分可以分为比例再保险和非比例再保险。所谓比例再保险,以保额为基础确定自留额和分保额。非比例再保险,以赔款为基础确定自负责任和分保责任。首先规定原保险人负担的赔偿限额(自留损失),超过这一限度的赔款由再保险人承担责任。

     

     

     

    收付费系统

    收付费系统是业务系统与财务系统的桥梁,业务系统中的相关收付信息(例如保费、赔款、分保费、再保赔款等)都需要通过接口送至收付系统。保费、赔款等费用的应收应付信息需要通过收付子系统以凭证的形式送至财务系统。实收保费、实付赔款等信息需要通过收付子系统以凭证的形式送至财务系统。

    香港那边称凭证为传票。

     

    单证管理系统

    保险公司的单证是保险业务使用的,有顺序编号的,经核保通过承担保险责任或者作为收款收据的有关凭证。一般包括保单、批单、保险标志、保险专用发票,对于交强险,有统一印制的交强险保单。从系统功能上讲一般包括版本登记、印刷入库、有号单证管理、级别设置等。有号单证管理里面会再包括申请领用登记、单证发放登记、取消发放、可使用登记、已使用登记、回收登记、回收登记确认、注销登记、作废登记等。

     

    反洗钱系统

    该系统的背景主要是配合07年1月1日实施的<<反洗钱法>>,为符合监管要求产生的系统。该系统了解不多,主要是从业务系统数据提取到大额数据接口表,再通过大额交易数据提取到将接口表数据转入大额交易数据表。

     

    事件管理系统

    事件管理系统(这里所说事件主要是指对于应用系统支持与维护)与需求管理系统,这里归类好像有问题,实质上来说它属于保险公司运维管理类系统。

    根据ITIL服务管理,事件管理是负责记录、归类和安排专家处理事故并监督整个处理过程直至事件被解决和终止的流程管理。事件管理的作用是快速有效地响应最终用户,使它们能够迅速恢复工作,以减小对最终用户的影响。事件管理包括故障管理,通过帮助台接线员以及二线技术人员的工作,迅速解决客户的故障。

     

    需求管理系统

    需求管理系统,目前没有接触过。

     

    后台提数程序

    后台提数程序,实质是最默默无闻的功臣,这么多保险系统如基本上都后台批处理程序在支持。像业务数据转储,交强险平台数据报送,理赔费用提取、各种中间平台业务数据提取等等,功能点比较多,就不列举啦。

     

    客户信息系统

    客户信息系统,对财险公司,比较难做,因为很多客户信息都掌握在代理人手上。保险公司内部人员流动也比较频繁,客户信息也随失丢失。另外,目前多数业务系统以保单为中心,对客户信息的收集也存在一定问题。包括客户基本信息管理, 客户关系信息和客户财务信息等。客户基本信息一般包括客户姓名、住址、联系方式、兴趣、关系人等方面的信息。客户关系信息,主要用于记录与客户的联系历史、客户与公司的业务信息、客户信息的修改历史和客户VIP级别等。客户财务信息,主要通过记录客户的收付记录可以了解客户的支付渠道及支付及时性信息,同时记录客户车辆、房产和账户信息可以为交叉销售及银联支付方式提供数据基础。

     

    行业渠道系统

    目前保险公司与第三方交互越来越多,通过邮政、银行等出单,已成为新的渠道。行业渠道系统就是这种背景下产生的,主要作用是作为第三方系统与保险公司业务系统的接口和桥梁。

     

    车型系统

    目前市场用的比较多是精友车型系统。刚上线的上海商业险平台用的也是精友数据,虽然看似功能比较小,但市场还是很大的。

     

    电子商务网站

    电子商务网站基本上保险公司的门面,感觉人保和平安的电子商务还是挺不错的。比较有实质的东西。特别是客户服务这块。平常要是查某些产品条款,基本上在人保电子商务网站上都有。

     

    零配件系统、车辆定核损系统和Gps查勘定损系统基本上都是从理赔系统中拆分出来的小产品。现在说明理赔的重要性。

    零配件系统,主要是维护各车型的零配件价格,在换件时,在定损环节以指导价。如系统中维护别克君威前保险杠外皮、前保险杠杠体、大灯总成(左)、车门(左)等不同地区不同的市场价或系统价等。

    车辆定核损系统,主要是把理赔系统的定损和核损两个环节拉出来,专门做定损系统。一般定损环节,直接开放给修理厂等机构,核损环节主要由保险公司内部客服部门。核损环节通过后,案件的核损金额带入到理赔赔付页面。

    GPS查勘系统,这边主要是针对理赔的调度和第一现场案件查勘。通过GPS卫星定位可以知道车辆出险区域查勘车辆位置,做出派工。这里还要结合短信平台和车载平台等。

    车险行业信息平台系统

    目前共有八个省市上车险信息共享平台。分别为上海、北京、浙江、江苏、辽宁、大连、宁波、湖南八家地区。目前主要是交强险信息上平台。商业险信息上平台估计随着上海的试点,其他地区也会陆续展开(08年是见费出单,今年估计是信息平台,深圳、四川、山东都已有所行动)。核心功能就是为保险公司的投保、批改和退保等提供保费计算、数据查询等服务。一般都有投保查询、投保确认、投保注销、批改询价、批改确认、批改注销、退保询价、退保确认等。

    目前的信息平台主要有两个,一个是中科软的全国信息平台,一个是易保的上海(北京)信息平台。这两个信息平台的主要负责人都很敬业认真负责的。上海商业险进平台,Helen都忙到晚上2点多。宁波车船税上平台,为了处理各家数据,项目经理李和其他同事也都是奋战通宵的。好像有些跑题,之所以写,希望看贴的朋友们,以后在工作中,不管是甲方还是乙方,大家应一起齐心协力,把事情做好。

     

    OA

    列在这里也不合适,不属于核心应用系统。了解不多。

     

    短信平台

    短信平台虽然不属于业务系统,但现在的承保和理赔业务都有短信平台关联的地方。重要性也比较高。涉及到对外客户,基本上不允许有什么错误。根据运营商不同,现在一般分为移动、联通和电信三个平台。

    展开全文
  • 内核初始化与gpio子系统

    千次阅读 2013-08-22 15:45:25
    定义在linux/arch/arm/plat-s3c/gpio-config.c int s3c_gpio_cfgpin(unsigned int pin, unsigned int config) { struct s3c_gpio_chip *chip = s3c_gpiolib_getchip(pin); //得到对应GPIO结构体首指针...

    定义在linux/arch/arm/plat-s3c/gpio-config.c

    int s3c_gpio_cfgpin(unsigned int pin, unsigned int config)

    {

    struct s3c_gpio_chip *chip = s3c_gpiolib_getchip(pin);  //得到对应GPIO结构体首指针,里面包含了该GPIO的各种参数

    unsigned long flags;

    int offset;

    int ret;

     offset = pin - chip->chip.base;    // 否则offset等于该GPIO引脚相对于GPX0)的偏移量,每个偏移1

    ret = s3c_gpio_do_setcfg(chip, offset, config);   //设置该GPIO状态寄存器的数值为config

    //s3c_gpio_do_setcfg操作

    static inline int s3c_gpio_do_setcfg(struct s3c_gpio_chip *chip,unsigned int off, unsigned int config)

    {

         return (chip->config->set_config)(chip, off, config);

    }

    //这里的set_config是一个函数指针,由后面的分析知道,如果针对GPA,该函数指针指向s3c_gpio_setcfg_s3c24xx_a ,如果针对GPX应该是指向s3c_gpio_setcfg_s3c24xx——但发现,如果是其他GPX,根本没有定义set_config!!!(这个问题已经解决,见后文s3c24xx_gpiolib_init函数,事实上,其余的config的确指向s3c_gpio_do_setcfg函数)

    struct s3c_gpio_cfg s3c24xx_gpiocfg_default = {

               .set_config = s3c_gpio_setcfg_s3c24xx,

               .get_config = s3c_gpio_getcfg_s3c24xx,

    };

     

    int s3c_gpio_setcfg_s3c24xx_a(struct s3c_gpio_chip *chip, unsigned int off, unsigned int cfg)

    {

    void __iomem *reg = chip->base;   // GPXCON的物理基地址

    unsigned int shift = off;    // 每个GPA对应一位

    u32 con;

     if (s3c_gpio_is_cfg_special(cfg)) {     //OUTPUT状态是否为(0xfffffffX),是,返回1

             cfg &= 0xf;  // cfg = 0xX

      /* Map output to 0, and SFN2 to 1 */ 本实验不会运行到这

    cfg -= 1;

    if (cfg > 1)

            return -EINVAL;

    cfg <<= shift;

    }

    con = __raw_readl(reg);     // 先读出该GPXCON的值,32

    con &= ~(0x1 << shift);     // 

    con |= cfg;                         //

     __raw_writel(con, reg);    // 将新值写入GPXCON

     

    PS:   

    #define __raw_writeb(v,a) (__chk_io_ptr(a), *(volatile unsigned char __force *)(a) = (v))

    #define __raw_writew(v,a) (__chk_io_ptr(a), *(volatile unsigned short __force *)(a) = (v))

    #define __raw_writel(v,a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a) = (v))

    #define __raw_readb(a) (__chk_io_ptr(a), *(volatile unsigned char __force *)(a))

    #define __raw_readw(a) (__chk_io_ptr(a), *(volatile unsigned short __force *)(a))

    #define __raw_readl(a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a))

    return 0;

    }

    如果针对GPX情况

    int s3c_gpio_setcfg_s3c24xx(struct s3c_gpio_chip *chip,

    unsigned int off, unsigned int cfg)

    {

    void __iomem *reg = chip->base;

     unsigned int shift = off * 2;    // 每个GPX对应2

    u32 con;

    if (s3c_gpio_is_cfg_special(cfg)) {

              cfg &= 0xf;

    if (cfg > 3)

              return -EINVAL;

      cfg <<= shift;        // cfg0,1两位左移offset

    }

     con = __raw_readl(reg);         // 读对应的GPXCON

     con &= ~(0x3 << shift);          // GPXCONpin)的两bits0

     con |= cfg;                               // 设置config

     __raw_writel(con, reg);           // 写入新的GPXCON

    return 0;

    }

     

    return ret;

    我们先来看s3c_gpiolib_getchip,它实现了返回对应pin值的GPIO结构体首指针的功能

    #include<mach/gpio-core.h> 

     static inline struct s3c_gpio_chip *s3c_gpiolib_getchip(unsigned int pin)

    {

    struct s3c_gpio_chip *chip;

     if (pin > S3C_GPIO_END)    //如果超过GPJ(32)return NULL

         return NULL;

    chip = &s3c24xx_gpios[pin/32];     //根据偏移,计算出对应pinGPIO结构体指针

         return ((pin - chip->chip.base) < chip->chip.ngpio) ? chip : NULL;

         //  这里验证,如果pin偏移超过了GPIO的个数,说明出错了,否则就返回该GPIO的结构体指针

    }

    回想以下之前s3c2410_gpio_cfgpin中,我们传入的参数是led_table[i]和 led_cfg_table[i]

    struct s3c_gpio_chip s3c24xx_gpios[] = {

    [0] = {

    .base = S3C2410_GPACON,  // datasheet上地址为0x56000000

    #define S3C2410_GPIOREG(x) ((x) + S3C24XX_VA_GPIO) 

    #define S3C24XX_VA_GPIO     ((S3C24XX_PA_GPIO - S3C24XX_PA_UART) + S3C24XX_VA_UART)

    S3C24XX_PA_GPIO相当于(0x15600000) 

    S3C24XX_PA_UART相当于(0x15000000) 

    #define S3C_VA_UART    S3C_ADDR(0x01000000)    /* UART */ 

    #define S3C_ADDR_BASE 0xF6000000

    #ifndef __ASSEMBLY__

    #define S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x))

    #else

    #define S3C_ADDR(x) (S3C_ADDR_BASE + (x))

    #endif

    0x15600000-15000000+F7000000这里的S3C2410_GPACON应该怎么算?

     

    .pm = __gpio_pm(&s3c_gpio_pm_1bit),

    .config = &s3c24xx_gpiocfg_banka,   // 设置GPIO的函数指针

                         static struct s3c_gpio_cfg s3c24xx_gpiocfg_banka = {

                             .set_config = s3c_gpio_setcfg_s3c24xx_a,

                             .get_config = s3c_gpio_getcfg_s3c24xx_a,

                          };

    .chip = {

    .base = S3C2410_GPA(0),   //基地址,也是偏移量

    .owner = THIS_MODULE,

    .label = "GPIOA",

    .ngpio = 24,

    .direction_input = s3c24xx_gpiolib_banka_input,

    .direction_output = s3c24xx_gpiolib_banka_output,

    },

    },

    [1] = {

    .base = S3C2410_GPBCON,

    .pm = __gpio_pm(&s3c_gpio_pm_2bit),

    .chip = {

    .base = S3C2410_GPB(0),

    .owner = THIS_MODULE,

    .label = "GPIOB",

    .ngpio = 16,

    },

    },

    [2] = {

    .base = S3C2410_GPCCON,

    .pm = __gpio_pm(&s3c_gpio_pm_2bit),

    .chip = {

    .base = S3C2410_GPC(0),

    .owner = THIS_MODULE,

    .label = "GPIOC",

    .ngpio = 16,

    },

    },

    [3] = {

    .base = S3C2410_GPDCON,

    .pm = __gpio_pm(&s3c_gpio_pm_2bit),

    .chip = {

    .base = S3C2410_GPD(0),

    .owner = THIS_MODULE,

    .label = "GPIOD",

    .ngpio = 16,

    },

    },

    [4] = {

    .base = S3C2410_GPECON,

    .pm = __gpio_pm(&s3c_gpio_pm_2bit),

    .chip = {

    .base = S3C2410_GPE(0),

    .label = "GPIOE",

    .owner = THIS_MODULE,

    .ngpio = 16,

    },

    },

    [5] = {

    .base = S3C2410_GPFCON,

    .pm = __gpio_pm(&s3c_gpio_pm_2bit),

    .chip = {

    .base = S3C2410_GPF(0),

    .owner = THIS_MODULE,

    .label = "GPIOF",

    .ngpio = 8,

    .to_irq = s3c24xx_gpiolib_bankf_toirq,

    },

    },

    [6] = {

    .base = S3C2410_GPGCON,

    .pm = __gpio_pm(&s3c_gpio_pm_2bit),

    .irq_base = IRQ_EINT8,

    .chip = {

    .base = S3C2410_GPG(0),

    .owner = THIS_MODULE,

    .label = "GPIOG",

    .ngpio = 16,

    .to_irq = samsung_gpiolib_to_irq,

    },

    }, {

    .base = S3C2410_GPHCON,

    .pm = __gpio_pm(&s3c_gpio_pm_2bit),

    .chip = {

    .base = S3C2410_GPH(0),

    .owner = THIS_MODULE,

    .label = "GPIOH",

    .ngpio = 11,

    },

    },

    /* GPIOS for the S3C2443 and later devices. */2440用不到

    {

    .base = S3C2440_GPJCON,

    .pm = __gpio_pm(&s3c_gpio_pm_2bit),

    .chip = {

    .base = S3C2410_GPJ(0),

    .owner = THIS_MODULE,

    .label = "GPIOJ",

    .ngpio = 16,

    },

    }, {

    .base = S3C2443_GPKCON,

    .pm = __gpio_pm(&s3c_gpio_pm_2bit),

    .chip = {

    .base = S3C2410_GPK(0),

    .owner = THIS_MODULE,

    .label = "GPIOK",

    .ngpio = 16,

    },

    }, {

    .base = S3C2443_GPLCON,

    .pm = __gpio_pm(&s3c_gpio_pm_2bit),

    .chip = {

    .base = S3C2410_GPL(0),

    .owner = THIS_MODULE,

    .label = "GPIOL",

    .ngpio = 15,

    },

    }, {

    .base = S3C2443_GPMCON,

    .pm = __gpio_pm(&s3c_gpio_pm_2bit),

    .chip = {

    .base = S3C2410_GPM(0),

    .owner = THIS_MODULE,

    .label = "GPIOM",

    .ngpio = 2,

    },

    },

    };

    *************************************************************************** 

    下面分析第二个函数,先看一下相关结构体

    void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)

    {

    /* do this via gpiolib until all users removed */

          gpio_request(pin, "temporary");

          gpio_set_value(pin, to);

          gpio_free(pin);

    }

    又出现了三个函数,我们一一说明:

    1169/* These "optional" allocation calls help prevent drivers from stomping

    1170 * on each other, and help provide better diagnostics in debugfs.

    1171 * They're called even less than the "set direction" calls.

    1172 */

    PS:static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];

    其中ARCH_NR_GPIOSarch/arm/mach-s3c2410/include/mach/gpio.h中定义

    #define ARCH_NR_GPIOS (32 * 9 + CONFIG_S3C24XX_GPIO_EXTRA)

    因此,每个引脚都分配了一个gpio_desc数据结构

    1173int gpio_request(unsigned gpio, const char *label)        // 这个函数还不是很明白

    1174{

    1175 struct gpio_desc *desc;

    1176 struct gpio_chip *chip;

    1177 int status = -EINVAL;

    1178 unsigned long flags;

    1179

    1180 spin_lock_irqsave(&gpio_lock, flags);    // gpio_lock是自旋锁,上锁,保存FLAGflags变量中

    1181

    1182 if (!gpio_is_valid(gpio))     // 不符合要求,跳转到done

    1183     goto done;

    1184 desc = &gpio_desc[gpio];    // desc = &gpio_desc[pin]

    1185 chip = desc->chip;

    1186 if (chip == NULL)              // gpio_desc.chip指向NULL,跳转到done

    1187     goto done;

    1188

    1189 if (!try_module_get(chip->owner))   // 该函数用于增加模块使用计数;若返回为0,表示调用失败,希望使用的模块没有被加载或正在被卸载中

    1190     goto done;

    1191

    1192 /* NOTE: gpio_request() can be called in early boot,

    1193 * before IRQs are enabled, for non-sleeping (SOC) GPIOs.

    1194 */

    1195

    1196 if (test_and_set_bit(FLAG_REQUESTED, &desc->flags) == 0) {    // 原子操作,将flags的第FLAG_REQUESTED位置1,并返回其原值

    1197     desc_set_label(desc, label ? : "?");    // 如果原来的值是0, 执行desc_set_label, desc->chip.label赋值,如果label有定义,直接用定义,比如上面的“temporary”,否则用“?”

    static inline void desc_set_label(struct gpio_desc *d, const char *label)

    {

    #ifdef CONFIG_DEBUG_FS

     d->label = label;               // 为什么不是d->chip.label = label; ?

    #endif

    }

    1198 status = 0;

    1199 } else {                      // 如果flags的第FLAG_REQUESTED位原来的值是1

    1200 status = -EBUSY;

    1201 module_put(chip->owner);    // 该函数用于减少模块使用计数

    1202     goto done;

    1203 }

    1204

    1205 if (chip->request) {    // chip->requestlinux初始化时是没有指向的,可以见后面s3c_gpiolib_add

    1206 /* chip->request may sleep */

    1207                    spin_unlock_irqrestore(&gpio_lock, flags);            // 如果chip->request不为0, 解锁,因为后面调用的chip->request有可能睡眠

    1208                    status = chip->request(chip, gpio - chip->base);

    1209                    spin_lock_irqsave(&gpio_lock, flags);                    // 执行完后,继续上锁

    1210

    1211                    if (status < 0) {        // status返回负数,说明出错

    1212                              desc_set_label(desc, NULL);

    1213                              module_put(chip->owner);

    1214                              clear_bit(FLAG_REQUESTED, &desc->flags);

    1215                     }

    1216 }

    1217

    1218done:

    1219 if (status)

    1220     pr_debug("gpio_request: gpio-%d (%s) status %d/n",  gpio, label ? : "?", status);

    1221    // 如果状态不为0, 打印gpio-pin"****"的状态

    1222 spin_unlock_irqrestore(&gpio_lock, flags);     // 解锁

    1223 return status;    // 返回状态

    1224}

    *************************************************************************** 

    下面先分析gpio_free函数

    void gpio_free(unsigned gpio)           // 待分析

    {

    unsigned long flags;

    struct gpio_desc *desc;

    struct gpio_chip *chip;

    might_sleep();

    if (!gpio_is_valid(gpio)) {

            WARN_ON(extra_checks);

            return;

    }

    gpio_unexport(gpio);

    spin_lock_irqsave(&gpio_lock, flags);

    desc = &gpio_desc[gpio];

    chip = desc->chip;

    if (chip && test_bit(FLAG_REQUESTED, &desc->flags)) {

         if (chip->free) {

              spin_unlock_irqrestore(&gpio_lock, flags);

              might_sleep_if(chip->can_sleep);

              chip->free(chip, gpio - chip->base);

              spin_lock_irqsave(&gpio_lock, flags);

          }

    desc_set_label(desc, NULL);

    module_put(desc->chip->owner);

    clear_bit(FLAG_ACTIVE_LOW, &desc->flags);

    clear_bit(FLAG_REQUESTED, &desc->flags);

    } else

    WARN_ON(extra_checks);

    spin_unlock_irqrestore(&gpio_lock, flags);

    }

    EXPORT_SYMBOL_GPL(gpio_free);

    *************************************************************************** 

    arch/arm/mach-s3c2410/include/mach/gpio.h 

    #define gpio_set_value __gpio_set_value 

    void __gpio_set_value(unsigned gpio, int value)

    {

    struct gpio_chip *chip;

    chip = gpio_to_chip(gpio);        // 返回对应于pingpio_desc[pin].chip指针

    WARN_ON(chip->can_sleep);

    chip->set(chip, gpio - chip->base, value);       // 这里调用的是s3c_gpiolib_set函数!!!

    }

    /* caller holds gpio_lock *OR* gpio is marked as requested */

    static inline struct gpio_chip *gpio_to_chip(unsigned gpio)

    {

          return gpio_desc[gpio].chip;

    }

    看到这里,一直有个问题让我百思不得其解,这里的chip按理说应该是s3c_gpio_chip中的chip成员,但是之前都没有代码交代他们是如何联系到一起的,s3c_gpio_chipgpio_desc结构体如何联系在一起,也没有函数交代,并且这里的chip->set函数指针也没有实现的代码,但是,经实验确认没有问题后,我开始查找plat-s3c24xx/gpiolib.c中的函数希望能有些线索,果然,找到了这么一个函数:

    static __init int s3c24xx_gpiolib_init(void)

    {

    struct s3c_gpio_chip *chip = s3c24xx_gpios;

    int gpn;

    for (gpn = 0; gpn < ARRAY_SIZE(s3c24xx_gpios); gpn++, chip++) {

            if (!chip->config)

                      chip->config = &s3c24xx_gpiocfg_default;          // 原来chip->config默认函数也是在这里!!!

            s3c_gpiolib_add(chip);           // 之前的疑惑都在这里实现!!!

    }

    return 0;

    }

    core_initcall(s3c24xx_gpiolib_init);

    但是,这个s3c24xx_gpiolib_init函数又是在什么时候执行的呢?可以看到,在该函数的下面,有一句:core_initcall(s3c24xx_gpiolib_init);查阅相关资料发现, 在linux初始化的过程中,内核采用了一种initcall的机制,它利用gcc的扩展功能以及ld的连接控制脚本实现了在内核初始化的过程中通过简单的循环就实现了相关驱动的初始化

    也就是说,在linux初始化期间,就已经执行了s3c24xx_gpiolib_init,现在我们可以分析下s3c_gpiolib_add(chip);这个函数了,

    __init void s3c_gpiolib_add(struct s3c_gpio_chip *chip)

    {

    struct gpio_chip *gc = &chip->chip;

    int ret;

    BUG_ON(!chip->base);

    BUG_ON(!gc->label);

    BUG_ON(!gc->ngpio);

     spin_lock_init(&chip->lock);     // 初始化s3c_gpio_chip的自旋锁

    if (!gc->direction_input)

               gc->direction_input = s3c_gpiolib_input;         // direction_input 函数指针

    if (!gc->direction_output)

               gc->direction_output = s3c_gpiolib_output;    // direction_output 函数指针

    if (!gc->set)

               gc->set = s3c_gpiolib_set;        // set函数指针

    if (!gc->get)

               gc->get = s3c_gpiolib_get;        // get函数指针

    #ifdef CONFIG_PM

    if (chip->pm != NULL) {

              if (!chip->pm->save || !chip->pm->resume)

                        printk(KERN_ERR "gpio: %s has missing PM functions/n", gc->label);

              } else

              printk(KERN_ERR "gpio: %s has no PM function/n", gc->label);

    #endif

    /* gpiochip_add() prints own failure message on error. */

    ret = gpiochip_add(gc);

    if (ret >= 0)

              s3c_gpiolib_track(chip);

    }

     

    gpiochip_add函数分析:

    int gpiochip_add(struct gpio_chip *chip)      // gpio_desc[]中分配空间,并链接chip结构

    {

    unsigned long flags;

    int status = 0;

    unsigned id;

    int base = chip->base;

    if ((!gpio_is_valid(base) || !gpio_is_valid(base + chip->ngpio - 1))

    && base >= 0) {

            status = -EINVAL;

            goto fail;

    }

     spin_lock_irqsave(&gpio_lock, flags);       // 上锁

    if (base < 0) {

             base = gpiochip_find_base(chip->ngpio);  // 这个函数在gpiolib.c中,在gpio_desc[]中分配chip->ngpio个空间(从最后往前分配),返回第一个index

             if (base < 0) {          // 分配不到

                    status = base;

                    goto unlock;      // 解锁退出

             }

             chip->base = base;    // gpio_chip *chip->base = i (igpio_desc[i]中的index)

    }

    /* these GPIO numbers must not be managed by another gpio_chip */

    for (id = base; id < base + chip->ngpio; id++) {

           if (gpio_desc[id].chip != NULL) {

                status = -EBUSY;

                break;

           }

    }

    if (status == 0) {         // 分配到空间,正常情况下

             for (id = base; id < base + chip->ngpio; id++) {

                  gpio_desc[id].chip = chip;    // 这里将gpio_descs3c_gpio_chip联系起来,他们的chip成员指向的是同一个数据结构

    /* REVISIT: most hardware initializes GPIOs as

    * inputs (often with pullups enabled) so power

    * usage is minimized. Linux code should set the

    * gpio direction first thing; but until it does,

    * we may expose the wrong direction in sysfs.

    */

                  gpio_desc[id].flags = !chip->direction_input ? (1 << FLAG_IS_OUT) : 0;  // 设置flags

              }

     } // end if

    of_gpiochip_add(chip);    // 没操作

    unlock:

     spin_unlock_irqrestore(&gpio_lock, flags);         // 解锁

    if (status)

          goto fail;

    status = gpiochip_export(chip);        //×××××××××××××××待分析

    if (status)

          goto fail;

    return 0;

    fail:

    /* failures here can mean systems won't boot... */

    pr_err("gpiochip_add: gpios %d..%d (%s) failed to register/n",

    chip->base, chip->base + chip->ngpio - 1,

    chip->label ? : "generic");

     return status;       // 返回状态

    }

    下面是s3c_gpiolib_track函数

    #ifdef CONFIG_S3C_GPIO_TRACK

    struct s3c_gpio_chip *s3c_gpios[S3C_GPIO_END];

    static __init void s3c_gpiolib_track(struct s3c_gpio_chip *chip)       // 没完全理解,待分析

    {

    unsigned int gpn;

    int i;

    gpn = chip->chip.base;

    for (i = 0; i < chip->chip.ngpio; i++, gpn++) {

               BUG_ON(gpn >= ARRAY_SIZE(s3c_gpios));

               s3c_gpios[gpn] = chip;

    }

    }

    #endif /* CONFIG_S3C_GPIO_TRACK */

    *************************************************************************** 

     好,现在我们开始分析设备注册与卸载函数,在初始化程序中,有如下语句:

     ret = misc_register(&misc); //注册设备

     其中的misc_register就是杂项设备的注册函数,首先关注下这里的参数misc数据结构

    75 /*

    76 * 把 LED 驱动注册为 MISC 设备

    77 */

    78 static struct miscdevice misc = {

    79           .minor = MISC_DYNAMIC_MINOR, //动态设备号

    80           .name = DEVICE_NAME,

    81           .fops = &dev_fops,

    82 };

    miscdevice的数据结构如图所示:

    int   misc_register(struct miscdevice * misc)

    {

    struct miscdevice *c;

    dev_t dev;

    int err = 0;

     INIT_LIST_HEAD(&misc->list);      // 初始化链表头,将misc->listnextpre都指向自己

     mutex_lock(&misc_mtx);           // 获取互斥锁, or睡眠

     list_for_each_entry(c, &misc_list, list) {    // 遍历整个misc_list链表,所有的杂项驱动设备都有一个miscdevice数据结构,这些杂项驱动设备通过一个全局的misc_list链表连在一起, 相当一个记录

            if (c->minor == misc->minor) {     // 如果misc_list中已经有了这个设备(minor相同),则解锁返回,这里c是遍历时的tmp miscdevice,指向当前遍历节点

                     mutex_unlock(&misc_mtx);

                     return -EBUSY;

             }

    }

     if (misc->minor == MISC_DYNAMIC_MINOR) {        // 如果misc_list中没有该设备,判断minor是否准备动态分配,实验中如此设置

            int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);   // misc_minors是杂项设备位图,总共有64个位DYNAMIC_MINORS=64,表示可以注册64个杂项设备,这句代码找到位图中的空闲位置(表示还能加新设备)

            if (i >= DYNAMIC_MINORS) {    // 如果超过总设备数,则解锁返回

                     mutex_unlock(&misc_mtx);

                     return -EBUSY;

            }

            misc->minor = DYNAMIC_MINORS - i - 1;       // 计算子设备号,赋值到misc->minor

            set_bit(i, misc_minors);        // 对应的位图也置位

    }

     dev = MKDEV(MISC_MAJOR, misc->minor);      // 生成设备号

    // sysfs中创建并注册一个设备,可以在/dev下面看到misc->name

    misc->this_device = device_create(misc_class, misc->parent, dev, misc, "%s", misc->name);

    1504struct device *device_create(struct class *class, struct device *parent,          // 这个函数以后会详细看

    1505 dev_t devt, void *drvdata, const char *fmt, ...)

    1506{

    1507 va_list vargs;

    1508 struct device *dev;

    1509

    1510 va_start(vargs, fmt);

    1511 dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);

    1512 va_end(vargs);

    1513 return dev;

    1514} 

     

    // this_device是在创建设备节点时指向函数device_create()返回的设备结构

     if (IS_ERR(misc->this_device)) {              // 如果创建节点出错,并且

           int i = DYNAMIC_MINORS - misc->minor - 1;        // 计算子设备号之前misc->minor的值

           if (i < DYNAMIC_MINORS && i >= 0)   // 计算位图位i,如果在0-64之间,说明在set_bit中置位了,则清楚位图,处理错误,准备返回

                 clear_bit(i, misc_minors);

                 err = PTR_ERR(misc->this_device);

           goto out;

    }

    /*

    * Add it to the front, so that later devices can "override"

    * earlier defaults

    */

     list_add(&misc->list, &misc_list);               // 以上操作都没有问题后,将新设备加入misc_list链表,解锁返回

    out:

    mutex_unlock(&misc_mtx);

    return err;

    }

    *************************************************************************** 

    同样,对应misc_register函数,在exit中会调用misc_deregister函数

    int misc_deregister(struct miscdevice *misc)

    {

    int i = DYNAMIC_MINORS - misc->minor - 1;

     if (WARN_ON(list_empty(&misc->list)))    // 如果该misc->listnext指向自己,则出错返回

         return -EINVAL;

     mutex_lock(&misc_mtx);       // 上锁

     list_del(&misc->list);            // miscmisc_list链表中删除

    device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));        // 对应device_create!

    1532void device_destroy(struct class *class, dev_t devt)

    1533{

    1534 struct device *dev;

    1535

    1536 dev = class_find_device(class, NULL, &devt, __match_devt);

    1537 if (dev) {

    1538 put_device(dev);

    1539 device_unregister(dev);

    1540 }

    1541}

    1542EXPORT_SYMBOL_GPL(device_destroy); 

    if (i < DYNAMIC_MINORS && i >= 0)

         clear_bit(i, misc_minors);       // 计算位图位i,如果在0-64之间,说明在set_bit中置位了,清楚位图

    mutex_unlock(&misc_mtx);         // 解锁返回

    return 0;

    }

    *************************************************************************** 

    总结杂项设备驱动的注册与卸载流程:

    misc_register:找到空闲设备位图位置 -> 计算子设备号(如果动态的话),位图位置位 - >  device_creat() -> miscdevice结构体加入misc_list链表中

    misc_deregister:  miscdevice结构体从misc_list链表中删除 -> device_destory() -> 位图位清零

    *************************************************************************** 

    s3c24xx_gpiolib_init函数一样,misc也有一个初始化函数会在linux初始化时运行,下面来分析这个函数

    static int __init misc_init(void)

    {

    int err;

    #ifdef CONFIG_PROC_FS    //proc文件系统下创建一个"misc"目录。 misc_proc_fops是该文件系统下文件的操作函数集

               proc_create("misc", 0, NULL, &misc_proc_fops);

    #endif

     misc_class = class_create(THIS_MODULE, "misc");   // 前面device_create()中的misc_class就是在这里初始化的

     err = PTR_ERR(misc_class);

     if (IS_ERR(misc_class))        // 出错处理

         goto fail_remove;

    err = -EIO;

    if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))   //注册一个主设备号为MISC_MAJOR10)的字符设备,设备操作函数集为misc_fops

    goto fail_printk;

    misc_class->devnode = misc_devnode;

    return 0;

    fail_printk:       // 错误处理

    printk("unable to get major %d for misc devices/n", MISC_MAJOR);

    class_destroy(misc_class);

    fail_remove:

    remove_proc_entry("misc", NULL);

    return err;

    }

    subsys_initcall(misc_init);









    整个arm linux内核的启动可分为三个阶段:第一阶段主要是进行cpu和体系结构的检查、cpu本身的初始化以及页表的建立等;第二阶段主要是对系统中的一些基础设施进行初始化;最后则是更高层次的初始化,如根设备和外部设备的初始化。
    1. 内核移植
    2.2. 涉及的头文件
    /linux-2.6.18.8/include
    [root@localhost include]# tree -L 1
    |-- asm-arm   ------------------------------->(1)
    |-- linux ------------------------------->(2)
    内核移植过程中涉及到的头文件包括处理器相关的头文件(1)和处理器无关的头文件(2)。
    2.3. 内核移植2.4. 涉及的源文件
    /linux-2.6.18.8/arch/arm
    [root@localhost arm]# tree -L 1
    |-- boot  ------------------------------->(2)
    |-- kernel  ------------------------------->(3)
    |-- mach-s3c2410   ------------------------------->(4)
    |-- mm    ------------------------------->(5)
    |-- tools    ------------------------------->(1)

    (1)/linux-2.6.18.8/arch/arm/tools
    [root@localhost tools]# tree -L 1
    .
    |-- Makefile
    |-- gen-mach-types
    `-- mach-types
    Mach-types 文件定义了不同系统平台的系统平台号。移植linux内核到新的平台上需要对新的平台登记系统平台号。
    Mach-types文件格式如下:
    # machine_is_xxx CONFIG_xxxx MACH_TYPE_xxx number
    s3c2410 ARCH_S3C2410 S3C2410          182
    smdk2410 ARCH_SMDK2410 SMDK2410            193
    之所以需要这些信息,是因为脚本文件linux/arch/arm/tools/gen-mach-types需要linux/arch/tools/mach-types来产生linux/include/asm-arm/mach-types.h文件,该文件中设置了一些宏定义,需要这些宏定义来为目标系统选择合适的代码。
    (2)linux-2.6.18.8/arch/arm/boot/compressed
    [root@localhost compressed]# tree -L 1
    |-- Makefile
    |-- Makefile.debug
    |-- big-endian.S
    |-- head-at91rm9200.S
    2 浅谈分析Arm linux 内核移植及系统初始化的过程 
      |-- head.S
    |-- ll_char_wr.S
    |-- misc.c
    |-- ofw-shark.c
    |-- piggy.S
    `-- vmlinux.lds.in
    Head.s 是内核映像的入口代码,是自引导程序。自引导程序包含一些初始化程序,这些程序都是体系结构相关的。在对系统作完初始化设置工作后,调用misc.c文件中的decompress_kernel()函数解压缩内核映像到指定的位置,然后跳转到kernel的入口地址。
    Vmlinux.lds.in用来生成内核映像的内存配置文件。
    (3)linux-2.6.18.8/arch/arm/kernel
    [root@localhost kernel]# tree -L 1
    |-- Makefile
    |-- apm.c
    |-- armksyms.c
    |-- arthur.c
    |-- asm-offsets.c
    |-- bios32.c
    |-- calls.S
    |-- dma.c
    |-- ecard.c
    |-- entry-armv.S
    |-- entry-common.S
    |-- entry-header.S
    |-- fiq.c
    |-- head-common.S
    |-- head-nommu.S
    |-- head.S
    |-- init_task.c
    |-- io.c
    |-- irq.c
    |-- isa.c
    |-- module.c
    |-- process.c
    |-- ptrace.c
    |-- ptrace.h
    |-- semaphore.c
    |-- setup.c
    |-- smp.c
    |-- sys_arm.c
    |-- time.c
    |-- traps.c
    `-- vmlinux.lds.S
    内核入口处也是由一段汇编语言实现的,由head.s和head-common.s两个文件组成。
    Head.s 是内核的入口文件, 在head.s的末尾处 #i nclude "head-common.S"。 经过一系列的初始化后,跳转到linux-2.6.18.8/init/main.c中的start_kernel()函数中,开始内核的基本初始化过程。

    /linux-2.6.18.8/init
    [root@localhost init]# tree
    |-- Kconfig
    |-- Makefile
    |-- calibrate.c
    |-- do_mounts.c
    |-- do_mounts.h
    |-- do_mounts_initrd.c
    |-- do_mounts_md.c
    |-- do_mounts_rd.c
    |-- initramfs.c
    |-- main.c
    `-- version.c
    (4)/linux-2.6.18.8/arch/arm/mach-s3c2410
    [root@localhost mach-s3c2410]# tree -L 1
    |-- Kconfig
    |-- Makefile
    |-- Makefile.boot
    |-- bast-irq.c
    |-- bast.h
    |-- clock.c
    |-- clock.h
    |-- common-smdk.c
    |-- common-smdk.h
    |-- cpu.c
    |-- cpu.h
    |-- devs.c
    |-- devs.h
    |-- dma.c
    |-- gpio.c
    |-- irq.c
    |-- irq.h
    |-- mach-anubis.c
    |-- mach-smdk2410.c
    |-- pm-simtec.c
    |-- pm.c
    |-- pm.h
    |-- s3c2400-gpio.c
    |-- s3c2400.h
    |-- s3c2410-clock.c
    |-- s3c2410-gpio.c
    |-- s3c2410.c
    |-- s3c2410.h
    |-- sleep.S
    |-- time.c
    |-- usb-simtec.c
    `-- usb-simtec.h
    这个目录中的文件都是板级相关的,其中比较重要是如下几个:
    linux/arch/arm/mach-s3c2410/cpu.c 
    linux/arch/arm/mach-s3c2410/common-smdk.c
    linux/arch/arm/mach-s3c2410/devs.c
    linux/arch/arm/mach-s3c2410/mach-smdk2410.c
    linux/arch/arm/mach-s3c2410/Makefile.boot
    linux/arch/arm/mach-s3c2410/s3c2410.c
    3. 处理器和设备4. 
    这里主要介绍处理器和设备的描述和操作过程。设备描述在linux/arch/arm/mach-s3c2410/devs.c和linux/arch/arm/mach-s3c2410/common-smdk.c中实现。最后以nand flash为例具体介绍。
      4.1. 处理器、设备4.2. 描述
    设备描述主要两个结构体完成:struct resource和struct platform_device。
    先来看看着两个结构体的定义:
    struct resource {
    resource_size_t start;
    resource_size_t end;
    const char *name;
    unsigned long flags;
    struct resource *parent, *sibling, *child;
    };
    Resource结构体主要是描述了设备在系统中的起止地址、名称、标志以及为了链式描述方便指向本结构体类型的指针。Resource定义的实例将被添加到platform_device结构体对象中去。
    struct platform_device {
    const char * name;
    u32 id;
    struct device dev;
    u32 num_resources;
    struct resource * resource;
    };
    Platform_device结构体包括结构体的名称、ID号、平台相关的信息、设备的数目以及上面定义的resource信息。Platform_device结构对象将被直接通过设备操作函数注册导系统中去。具体注册和注销过程在下一节介绍。
    4.3. 处理器、设备4.4. 操作
    (1) int platform_device_register(struct platform_device * pdev);    注册设备
    (2) void platform_device_unregister(struct platform_device * pdev); 注销设备
    (3) int platform_add_devices(struct platform_device **devs, int num);添加设备,通过调用上面两个函数实现。
    4.5. 添加Nand flash设备4.6. 
    下面以nand flash 设备的描述为例,具体介绍下设备的描述和注册过程。
    // resource结构体实例s3c_nand_resource 对nand flash 控制器描述,包括控制器的起止地址和标志。
    static struct resource s3c_nand_resource[] = {
    [0] = {
    .start = S3C2410_PA_NAND,
    .end   = S3C2410_PA_NAND + S3C24XX_SZ_NAND - 1,
    .flags = IORESOURCE_MEM,
    }
    };
    //platform_device结构体实例s3c_device_nand定义了设备的名称、ID号并把resource对象作为其成员之一。
    struct platform_device s3c_device_nand = {
    .name   = "s3c2410-nand",
    .id   = -1,
    .num_resources   = ARRAY_SIZE(s3c_nand_resource),
    .resource   = s3c_nand_resource,
    };
    // nand flash 的分区情况,由mtd_partition结构体定义。
    static struct mtd_partition smdk_default_nand_part[] = {
    [0] = {
    .name = "Boot Agent",
    .size = SZ_16K,
    .offset = 0,
    },
    [1] = {
    .name = "S3C2410 flash partition 1",
    .offset = 0,
    .size = SZ_2M,
    },
    [2] = {
    .name = "S3C2410 flash partition 2",
    .offset = SZ_4M,
    .size = SZ_4M,
    },
    [3] = {
    .name = "S3C2410 flash partition 3",
    .offset = SZ_8M,
    .size = SZ_2M,
    },
    [4] = {
    .name = "S3C2410 flash partition 4",
    4、浅谈分析Arm linux 内核移植及系统初始化的过程 
     .offset = SZ_1M * 10,
    .size = SZ_4M,
    },
    [5] = {
    .name = "S3C2410 flash partition 5",
    .offset = SZ_1M * 14,
    .size = SZ_1M * 10,
    },
    [6] = {
    .name = "S3C2410 flash partition 6",
    .offset = SZ_1M * 24,
    .size = SZ_1M * 24,
    },
    [7] = {
    .name = "S3C2410 flash partition 7",
    .offset = SZ_1M * 48,
    .size = SZ_16M,
    }
    };

    static struct s3c2410_nand_set smdk_nand_sets[] = {
    [0] = {
    .name = "NAND",
    .nr_chips = 1,
    .nr_partitions = ARRAY_SIZE(smdk_default_nand_part),
    .partitions = smdk_default_nand_part,
    },
    };static struct s3c2410_platform_nand smdk_nand_info = {
    .tacls = 20,
    .twrph0 = 60,
    .twrph1 = 20,
    .nr_sets = ARRAY_SIZE(smdk_nand_sets),
    .sets = smdk_nand_sets,
    };
    /* devices we initialise */
    // 最后将nand flash 设备加入到系统即将注册的设备集合中。 
    static struct platform_device __initdata *smdk_devs[] = {
    &s3c_device_nand,
    &smdk_led4,
    &smdk_led5,
    &smdk_led6,
    &smdk_led7,
    };
    然后通过smdk_machine_init()函数,调用设备添加函数platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs)) 完成设备的注册。具体过程参见系统初始化的相关部分。
    5. 系统初始化
    5.1. 系统初始化的主干线
    Start_kernel() èsetup_arch() èreset_init() è kernel_thread(init …) è init() è do_basic_setup() èdriver_init() è do_initcall()
    Start_kernel()函数负责初始化内核各个子系统,最后调用reset_init(),启动一个叫做init的内核线程,继续初始化。Start_kernel()函数在init/main.c中实现。
    asmlinkage void __init start_kernel(void)
    {
    char * command_line;
    extern struct kernel_param __start___param[], __stop___param[];
    smp_setup_processor_id();
    lockdep_init();
    local_irq_disable();
    early_boot_irqs_off();
    early_init_irq_lock_class();
    lock_kernel();
    boot_cpu_init();
    page_address_init();
    printk(KERN_NOTICE);
    printk(linux_banner);
    setup_arch(&command_line); 

    5、浅谈分析Arm linux 内核移植及系统初始化的过程
    setup_per_cpu_areas();
    smp_prepare_boot_cpu(); 
    sched_init();
    preempt_disable();
    build_all_zonelists();
    page_alloc_init();
    printk(KERN_NOTICE "Kernel command line: %s/n", saved_command_line);
    parse_early_param();
    parse_args("Booting kernel", command_line, __start___param,
       __stop___param - __start___param,
       &unknown_bootoption);
    sort_main_extable();
    unwind_init();
    trap_init();
    rcu_init();
    init_IRQ();
    pidhash_init();
    init_timers();
    hrtimers_init();
    softirq_init();
    timekeeping_init();
    time_init();
    profile_init();
    early_boot_irqs_on();
    local_irq_enable();

    console_init();
    if (panic_later)
    panic(panic_later, panic_param);

    lockdep_info();

    locking_selftest();

    #ifdef CONFIG_BLK_DEV_INITRD
    if (initrd_start && !initrd_below_start_ok &&
    initrd_start < min_low_pfn << PAGE_SHIFT) {
    printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
     
    6、浅谈分析Arm linux 内核移植及系统初始化的过程  咨询QQ:313807838
         "disabling it./n",initrd_start,min_low_pfn << PAGE_SHIFT);
    initrd_start = 0;
    }
    #endif
    vfs_caches_init_early();
    cpuset_init_early();
    mem_init();
    kmem_cache_init();
    setup_per_cpu_pageset();
    numa_policy_init();
    if (late_time_init)
    late_time_init();
    calibrate_delay();
    pidmap_init();
    pgtable_cache_init();
    prio_tree_init();
    anon_vma_init();
    #ifdef CONFIG_X86
    if (efi_enabled)
    efi_enter_virtual_mode();
    #endif
    fork_init(num_physpages);
    proc_caches_init();
    buffer_init();
    unnamed_dev_init();
    key_init();
    security_init();
    vfs_caches_init(num_physpages);
    radix_tree_init();
    signals_init();
    /* rootfs populating might need page-writeback */
    page_writeback_init();
    #ifdef CONFIG_PROC_FS
    proc_root_init();
    #endif
    cpuset_init();
    taskstats_init_early();
    delayacct_init();

    check_bugs();

    acpi_early_init(); /* before LAPIC and SMP init */

    /* Do the rest non-__init'ed, we're now alive */
    rest_init();
    }

    分析start_kernel()源码, 其中setup_arch() 和 reset_init()是两个比较关键的函数。下面将具体分析这两个函数。
    5.2. setup_arch()函数分析
    首先我们来分析下setup_arch()函数。
    Setup_arch()函数主要工作是安装cpu和machine,并为start_kernel()后面的初始化函数指针指定值。
    其中setup_processor()函数调用linux/arch/arm/kernel/head_common.S 中的lookup_processor_type函数查询处理器的型号并安装。

    Setup_machine()函数调用inux/arch/arm/kernel/head_common.S 中的lookup_machine_type(__machine_arch_type)函数根据体系结构号__machine_arch_type,在__arch_info_begin和__arch_info_end段空间查询体系结构。问题是__machine_arch_type是在什么时候赋的初值?__arch_info_begin和__arch_info_end段空间到底放的是什么内容?
    __machine_arch_type是一个全局变量,在linux/boot/decompress/misc.c的解压缩函数中得以赋值。
    decompress_kernel(ulg output_start, ulg free_mem_ptr_p, ulg free_mem_ptr_end_p, int arch_id)
    {
    __machine_arch_type = arch_id;
    }

    __arch_info_begin和__arch_info_end段空间到底放的内容由链接器决定,存放是.arch.info.init段的内容。这个段是通过段属性__attribute__指定的。Grep一下.arch.info.init 得到./include/asm/mach/arch.h:53: __attribute__((__section__(".arch.info.init"))) = {       / 在linux/include/asm-arm/mach/arch.h 中发现MACHINE_START宏定义。

    #define MACHINE_START(_type,_name) /
    static const struct machine_desc __mach_desc_##_type /
     __attribute_used__ /
     __attribute__((__section__(".arch.info.init"))) = { /
    .nr = MACH_TYPE_##_type, /
    .name = _name,

    #define MACHINE_END /
    };

    inux/arch/arm/mach-s3c2410/mach-smdk2410.c中对.arch.info.init段的初始化如下。
    7、浅谈分析Arm linux 内核移植及系统初始化的过程  
     MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch
        * to SMDK2410 */
    /* Maintainer: Jonas Dietsche */
    .phys_io = S3C2410_PA_UART,
    .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
    .boot_params = S3C2410_SDRAM_PA + 0x100,
    .map_io = smdk2410_map_io,
    .init_irq = s3c24xx_init_irq,
    .init_machine = smdk_machine_init,
    .timer = &s3c24xx_timer,
    MACHINE_END

    由此可见在.arch.info.init段内存放了__desc_mach_desc_SMDK2410结构体。初始化了相应的初始化函数指针。问题又来了, 这些初始化指针函数是什么时候被调用的呢?
    分析发现,不一而同。
    如s3c24xx_init_irq()函数是通过start_kernel()里的init_IRQ()函数调用init_arch_irq()实现的。因为在MACHINE_START结构体中  .init_irq = s3c24xx_init_irq,而在setup_arch()函数中init_arch_irq = mdesc->init_irq, 所以调用init_arch_irq()就相当于调用了s3c24xx_init_irq()。
    又如smdk_machine_init()函数的初始化。在MACHINE_START结构体中,函数指针赋值,.init_machine = smdk_machine_init。而init_machine()函数被linux/arch/arm/kernel/setup.c文件中的customize_machine()函数调用并被arch_initcall(Fn)宏处理,arch_initcall(customize_machine)。 被arch_initcall(Fn)宏处理过函数将linux/init/main.c
    do_initcalls()函数调用。 具体参看下边的部分。

    void __init setup_arch(char **cmdline_p)
    {
    struct tag *tags = (struct tag *)&init_tags;
    struct machine_desc *mdesc;
    char *from = default_command_line;

    setup_processor();
    mdesc = setup_machine(machine_arch_type);//machine_arch_type =SMDK2410  by edwin
    machine_name = mdesc->name;

    if (mdesc->soft_reboot)
    reboot_setup("s");

    if (mdesc->boot_params)
    tags = phys_to_virt(mdesc->boot_params);

    /*
     * If we have the old style parameters, convert them to
     * a tag list.
     */
    if (tags->hdr.tag != ATAG_CORE)
    convert_to_tag_list(tags);
    if (tags->hdr.tag != ATAG_CORE)
    tags = (struct tag *)&init_tags;

    if (mdesc->fixup)
    mdesc->fixup(mdesc, tags, &from, &meminfo);

    if (tags->hdr.tag == ATAG_CORE) {
    if (meminfo.nr_banks != 0)
    squash_mem_tags(tags);
    parse_tags(tags);
    }

    init_mm.start_code = (unsigned long) &_text;
    init_mm.end_code   = (unsigned long) &_etext;
    init_mm.end_data   = (unsigned long) &_edata;
    init_mm.brk    = (unsigned long) &_end;

    memcpy(saved_command_line, from, COMMAND_LINE_SIZE);
      
    8、浅谈分析Arm linux 内核移植及系统初始化的过程  咨询QQ:313807838
     saved_command_line[COMMAND_LINE_SIZE-1] = '/0';
    parse_cmdline(cmdline_p, from);
    paging_init(&meminfo, mdesc);
    request_standard_resources(&meminfo, mdesc);

    #ifdef CONFIG_SMP
    smp_init_cpus();
    #endif

    cpu_init();

    /*
     * Set up various architecture-specific pointers
     */
    init_arch_irq = mdesc->init_irq;
    system_timer = mdesc->timer;
    init_machine = mdesc->init_machine;

    #ifdef CONFIG_VT
    #if defined(CONFIG_VGA_CONSOLE)
    conswitchp = &vga_con;
    #elif defined(CONFIG_DUMMY_CONSOLE)
    conswitchp = &dummy_con;
    #endif
    #endif
    }
    5.3. rest_init()函数分析
    下面我们来分析下rest_init()函数。
    Start_kernel()函数负责初始化内核各子系统,最后调用reset_init(),启动一个叫做init的内核线程,继续初始化。在init内核线程中,将执行下列init()函数的程序。Init()函数负责完成根文件系统的挂接、初始化设备驱动程序和启动用户空间的init进程等重要工作。

    static void noinline rest_init(void)
    __releases(kernel_lock)
    {
    kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);
    numa_default_policy();
    unlock_kernel();

    preempt_enable_no_resched();
    schedule();
    preempt_disable();

    cpu_idle();
    }
    static int init(void * unused)
    {
    lock_kernel();
    set_cpus_allowed(current, CPU_MASK_ALL);
    child_reaper = current;

    smp_prepare_cpus(max_cpus);

    do_pre_smp_initcalls();

    smp_init();
    sched_init_smp();
    cpuset_init_smp();
    populate_rootfs();   //挂接根文件系统
    do_basic_setup();   //初始化设备驱动程序
      //启动用户空间的init进程
     
    9、浅谈分析Arm linux 内核移植及系统初始化的过程 
    if (!ramdisk_execute_command)
    ramdisk_execute_command = "/init";

    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
    ramdisk_execute_command = NULL;
    prepare_namespace();
    }

    /*
     * Ok, we have completed the initial bootup, and
     * we're essentially up and running. Get rid of the
     * initmem segments and start the user-mode stuff..
     */
    free_initmem();
    unlock_kernel();
    mark_rodata_ro();
    system_state = SYSTEM_RUNNING;
    numa_default_policy();

    if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
    printk(KERN_WARNING "Warning: unable to open an initial console./n");

    (void) sys_dup(0);
    (void) sys_dup(0);

    if (ramdisk_execute_command) {
    run_init_process(ramdisk_execute_command);
    printk(KERN_WARNING "Failed to execute %s/n",
    ramdisk_execute_command);
    }
    if (execute_command) {
    run_init_process(execute_command);
    printk(KERN_WARNING "Failed to execute %s.  Attempting "
    "defaults.../n", execute_command);
    }
    run_init_process("/sbin/init");
    run_init_process("/etc/init");
    run_init_process("/bin/init");
    run_init_process("/bin/sh");

    panic("No init found.  Try passing init= option to kernel.");
    }

    5.3.1. 挂接根文件系统
    Linux/init/ramfs.c
    void __init populate_rootfs(void)
    {
    char *err = unpack_to_rootfs(__initramfs_start,
     __initramfs_end - __initramfs_start, 0);
    if (err)
    panic(err);
    #ifdef CONFIG_BLK_DEV_INITRD
    if (initrd_start) {
    #ifdef CONFIG_BLK_DEV_RAM
    int fd;
    printk(KERN_INFO "checking if image is initramfs...");
    err = unpack_to_rootfs((char *)initrd_start,
    initrd_end - initrd_start, 1);
    if (!err) {
    printk(" it is/n");
    unpack_to_rootfs((char *)initrd_start,
    initrd_end - initrd_start, 0);
    free_initrd();
    return;
    }

    10、浅谈分析Arm linux 内核移植及系统初始化的过程  咨询QQ:313807838 
     fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700);
    if (fd >= 0) {
    sys_write(fd, (char *)initrd_start,
    initrd_end - initrd_start);
    sys_close(fd);
    free_initrd();
    }
    #else
    printk(KERN_INFO "Unpacking initramfs...");
    err = unpack_to_rootfs((char *)initrd_start,
    initrd_end - initrd_start, 0);
    if (err)
    panic(err);
    printk(" done/n");
    free_initrd();
    #endif
    }
    #endif
    }
    5.3.2. 初始化设备5.3.3. 驱动程序
    linux/init/main.c
    static void __init do_basic_setup(void)
    {
    /* drivers will send hotplug events */
    init_workqueues();
    usermodehelper_init();
    driver_init();   /* 初始化驱动程序模型。调用驱动初始化函数初始化子系统。 */

    #ifdef CONFIG_SYSCTL
    sysctl_init();
    #endif

    do_initcalls();
    }
    linux/init/main.c
    extern initcall_t __initcall_start[], __initcall_end[];

    static void __init do_initcalls(void)
    {
    initcall_t *call;
    int count = preempt_count();

    for (call = __initcall_start; call < __initcall_end; call++) {
    char *msg = NULL;
    char msgbuf[40];
    int result;

    if (initcall_debug) {
    printk("Calling initcall 0x%p", *call);
    print_fn_deor_symbol(": %s()",
    (unsigned long) *call);
    printk("/n");
    }

    result = (*call)();

    ……
    ……
    ……
    }

    /* Make sure there is no pending stuff from the initcall sequence */
    flush_scheduled_work();
    }
    分析上面一段代码可以看出,设备的初始化是通过do_basic_setup()函数调用do_initcalls()函数,实现__initcall_start, __initcall_end段之间的指针函数执行的。而到底是那些驱动函数怎么会被集中到这个段内的呢?我们知道系统内存空间的分配是由链接器ld读取链接脚本文件决定。链接器将同样属性的文件组织到相同的段里面去,如所有的.text段都被放在一起。在链接脚本里面可以获得某块内存空间的具体地址。我们来看下linux-2.6.18.8/arch/arm/kernel/vmlinux.lds.S文件。由于文件过长,只贴出和__initcall_start, __initcall_end相关的部分。
    __initcall_start = .;
    *(.initcall1.init)
    *(.initcall2.init)
    *(.initcall3.init)
    *(.initcall4.init)
    *(.initcall5.init)
    *(.initcall6.init)
    *(.initcall7.init)
    __initcall_end = .;
    从脚本文件中我们可以看出, 在__initcall_start, __initcall_end之间放置的是属行为(.initcall*.init)的函数数据 。在linux/include/linux/init.h文件中可以知道,(.initcall*.init)属性是由__define_initcall(level, fn)宏设定的。
    #define __define_initcall(level,fn) /
    static initcall_t __initcall_##fn __attribute_used__ /
    11、浅谈分析Arm linux 内核移植及系统初始化的过程 咨询QQ:313807838 
     __attribute__((__section__(".initcall" level ".init"))) = fn
    #define core_initcall(fn) __define_initcall("1",fn)
    #define postcore_initcall(fn) __define_initcall("2",fn)
    #define arch_initcall(fn) __define_initcall("3",fn)
    #define subsys_initcall(fn) __define_initcall("4",fn)
    #define fs_initcall(fn) __define_initcall("5",fn)
    #define device_initcall(fn) __define_initcall("6",fn)
    #define late_initcall(fn) __define_initcall("7",fn)
    #define __initcall(fn)      device_initcall(fn)


    内核提供了一个重要的结构体struct machine_desc ,通过machine_desc结构体来控制系统体系架构相关部分的初始化。machine_desc结构体的成员包含了体系架构相关部分的几个最重要的初始化函数,包括map_io,init_irq, init_machine以及phys_io , timer成员等。

    machine_desc结构体定义如下:

    struct machine_desc {

                    unsigned int                  nr;         /* architecture number         */

                    unsigned int                  phys_io;          /* start of physical io         */

                    unsigned int                  io_pg_offst; /* byte offset for io * page tabe entry         */

                    const char                 *name;          /* architecture name          */

                    unsigned long                     ;boot_params;          /* tagged list          */

                    unsigned int                  video_start;         /* start of video RAM          */

                    unsigned int                  video_end;         /* end of video RAM          */

                    unsigned int                 reserve_lp0 :1;          /* never has lp0          */

                    unsigned int                 reserve_lp1 :1;          /* never has lp1          */

                    unsigned int                  reserve_lp2 :1;          /* never has lp2          */

                    unsigned int                  soft_reboot :1;          /* soft reboot          */

                    void                          (*fixup)(struct machine_desc *,

                                                            struct tag *, char **,

                                                            struct meminfo *);

                    void                         (*map_io)(void);/* IO mapping function          */

                    void                         (*init_irq)(void);

                    struct sys_timer          *timer;          /* system tick timer          */

                    void                         (*init_machine)(void);

            };

    machine_desc结构体通过MACHINE_START宏来初始化,这里以s3c2410平台为例:

    s3c2410 machine_desc结构体定义如下:

            /* arch/arm/mach-s3c2410/mach-smdk2410.c */

            MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch

                                            * to SMDK2410 */

                    /* Maintainer: Jonas Dietsche */

                    .phys_io = S3C2410_PA_UART,

                    .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,

                    .boot_params = S3C2410_SDRAM_PA + 0x100,

                    .map_io = smdk2410_map_io,

                    .init_irq = s3c24xx_init_irq,

                    .init_machine = smdk2410_init,

                    .timer = &s3c24xx_timer,

            MACHINE_END

    其中的宏MACHINE_STARTMACHINE_END定义如下:

            #define MACHINE_START(_type,_name) \

            const struct machine_desc __mach_desc_##_type \

              __attribute__((__section__(".arch.info.init"))) = { \

                    .nr = MACH_TYPE_##_type, \

                    .name = _name,

    #define MACHINE_END \

            };

    MACHINE_START主要是定义了"struct machine_desc"的类型,放在 section(".arch.info.init"),是初始化数据,其所占用的内存在内核起来之后将会被释放。

    这里的map_io成员即内核提供给用户的创建外设I/O资源到内核虚拟地址静态映射表的接口函数。

    map_io成员函数会在系统初始化过程中被调用,流程如下:

            start_kernel -> setup_arch() --> paging_init()中被调用

            struct machine_desc 结构体的各个成员函数在不同时期被调用:

            1. .init_machine 在 arch/arm/kernel/setup.c 中被 customize_machine 调用,放在 arch_initcall( ) 段里面,会自动按顺序被调用(另外博客分析,敬请关注)。

            2. init_irqstart_kernel( ) --> init_IRQ( ) --> init_arch_irq( ) 被调用

            3. map_io 在 setup_arch( ) --> paging_init( )被调用

    其他主要都在 setup_arch() 中用到。

    用户可以在定义machine_desc结构体时指定map_io的接口函数,我们也正是这样做的。

    接下来我们继续分析smdk2410_map_io的执行过程,流程如下:

    smdk2410_map_io-> s3c24xx_init_io(smdk2410_iodesc, ARRAY_SIZE(smdk2410_iodesc))

    下面来看一下s3c24xx_init_io函数:

    void __init s3c24xx_init_io(struct map_desc *mach_desc, int mach_size)

            {

                    /* register our io-tables */

                    iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc));

                    ……

            }

    iotable_init内核提供,定义如下:

    /*

            * Create the architecture specific mappings

            */

            void __init iotable_init(struct map_desc *io_desc, int nr)

            {

                    int i;

                    for (i = 0; i nr; i++)

                    create_mapping(io_desc + i);

            }

    由上知道,smdk2410_map_io最终调用iotable_init建立映射表。

    iotable_init函数的参数有两个:一个是map_desc类型的结构体,另一个是该结构体的数量nr。这里最关键的就是struct map_descmap_desc结构体定义如下:

    /* include/asm-arm/mach/map.h */

                    struct map_desc {

                    unsigned long virtual;

                    unsigned long physical;

                    unsigned long length;

                    unsigned int type;

            };

    create_mapping( )函数就是通过map_desc提供的信息创建线性映射表的。

    这样的话我们就知道了创建I/O映射表的大致流程为:只要定义相应I/O资源的map_desc结构体,并将该结构体传给iotable_init函数执行,就可以创建相应的I/O资源到内核虚拟地址空间的映射表了。

    我们来看看s3c2410是怎么定义map_desc结构体的(即上面iotable_init()函数内的s3c_iodesc)

    [arch/arm/mach-s3c2410/cpu.c]

            /* minimal IO mapping */

            static struct map_desc s3c_iodesc[] __initdata = {

                    IODESC_ENT(GPIO),

                    IODESC_ENT(IRQ),

                    IODESC_ENT(MEMCTRL),

                    IODESC_ENT(UART)

            };

    IODESC_ENT宏如下:

    #define IODESC_ENT(x) { (unsigned long)S3C24XX_VA_##x, S3C2410_PA_##x, S3C24XX_SZ_##x, MT_DEVICE }

    展开后等价于:

    static struct map_desc s3c_iodesc[] __initdata = {

                    {

                            .virtual = S3C24XX_VA_GPIO,

                            .physical = S3C24XX_PA_GPIO,

                            .length = S3C24XX_SZ_GPIO,

                            .type = MT_DEVICE

                    },

                    ……

            };

    至此,我们可以比较清晰看到GPIO被静态映射的过程,由于我们在前面的静态映射中已经做好了GPIO的映射,也就是我们写GPIO相关驱动的时候可以如下配置引脚的原因:

            s3c2410_gpio_cfgpinxxx,xxx);

    其实,s3c2410_gpio_cfgpin定义如下:

    void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function)

            {

                    void __iomem *base = S3C2410_GPIO_BASE(pin);

                    unsigned long mask;

                    unsigned long con;

                    unsigned long flags;

            if (pin < S3C2410_GPIO_BANKB) {

                            mask = 1 << S3C2410_GPIO_OFFSET(pin);

                    } else {

                            mask = 3 << S3C2410_GPIO_OFFSET(pin)*2;

                    }

            local_irq_save(flags);

            con = __raw_readl(base + 0x00);

                    con &= ~mask;

                    con |= function;

            __raw_writel(con, base + 0x00);

            local_irq_restore(flags);

            }

    其中,比较关键的一个地方:

            void __iomem *base = S3C2410_GPIO_BASE(pin);

            这一行中,S3C2410_GPIO_BASE定义如下:

    #define S3C2410_GPIO_BASE(pin) ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)

    至此,GPIO的静态映射就看得很明白了。

    下面来看其他外设的静态映射:

    s3c24xx_init_io()函数中,除了iotable_init()以为,还会在最后调用,

            (cpu->map_io)(mach_desc, size);

    CPU的这个map_ioarch/arm/mach-s3c2410/cpu.c里面定义如下:

    static struct cpu_table cpu_ids[] __initdata = {

                    {

                            .idcode = 0x32410000,

                            .idmask = 0xffffffff,

                            .map_io = s3c2410_map_io,

                            .init_clocks = s3c2410_init_clocks,

                            .init_uarts = s3c2410_init_uarts,

                            .init = s3c2410_init,

                            .name = name_s3c2410

                    },

                            ...

             }

    再查看s3c2410_map_io(),函数代码如下:

    void __init s3c2410_map_io(struct map_desc *mach_desc, int mach_size)

            {

                    /* register our io-tables */

            iotable_init(s3c2410_iodesc, ARRAY_SIZE(s3c2410_iodesc));

                    iotable_init(mach_desc, mach_size);

            }

    接下来看结构s3c2410_iodesc [arch/arm/mach-s3c2410/s3c2410.c],代码如下,

    /* Initial IO mappings */

            static struct map_desc s3c2410_iodesc[] __initdata = {

                    IODESC_ENT(USBHOST),

                    IODESC_ENT(USBDEV),

                    IODESC_ENT(CLKPWR),

                    IODESC_ENT(LCD),

                    IODESC_ENT(TIMER),

                    IODESC_ENT(ADC),

                    IODESC_ENT(WATCHDOG),

            };

    赫然发现IODESC_ENT(TIMER)这一行,结合之前GPIO的类似分析,IODESC_ENT宏如下:

            #define IODESC_ENT(x) { (unsigned long)S3C24XX_VA_##x, S3C2410_PA_##x, S3C24XX_SZ_##x, MT_DEVICE }

    至此,TIMER, USBHOST,USBDEV,lcd,adc,watchdog等的静态映射都看得很明白了。




    gpio使用0~MAX_INT之间的整数标识,linux有一个框架处理gpio,能够使用统一的接口来操作gpio
    二 内核中gpio的使用
         1 测试gpio端口是否合法 int gpio_is_valid(int number); 
         2 申请某个gpio端口当然在申请之前需要显示的配置该gpio端口的pinmux
            int gpio_request(unsigned gpio, const char *label)
         3 标记gpio的使用方向包括输入还是输出
           /*成功返回零失败返回负的错误值*/ 
           int gpio_direction_input(unsigned gpio); 
           int gpio_direction_output(unsigned gpio, int value); 
         4 获得gpio引脚的值和设置gpio引脚的值(对于输出)
            int gpio_get_value(unsigned gpio);
            void gpio_set_value(unsigned gpio, int value); 
         5 gpio当作中断口使用
            int gpio_to_irq(unsigned gpio); 
            返回的值即中断编号可以传给request_irq()和free_irq()
            内核通过调用该函数将gpio端口转换为中断,在用户空间也有类似方法
         6 导出gpio端口到用户空间
            int gpio_export(unsigned gpio, bool direction_may_change); 
            内核可以对已经被gpio_request()申请的gpio端口的导出进行明确的管理,
            参数direction_may_change表示用户程序是否允许修改gpio的方向,假如可以
            则参数direction_may_change为真
            /* 撤销GPIO的导出 */ 
            void gpio_unexport(); 
    三 用户空间gpio的调用 
              用户空间访问gpio,即通过sysfs接口访问gpio,下面是/sys/class/gpio目录下的三种文件: 
                --export/unexport文件
                --gpioN指代具体的gpio引脚
                --gpio_chipN指代gpio控制器
                必须知道以上接口没有标准device文件和它们的链接。 
     (1) export/unexport文件接口:
                   /sys/class/gpio/export,该接口只能写不能读
                   用户程序通过写入gpio的编号来向内核申请将某个gpio的控制权导出到用户空间当然前提是没有内核代码申请这个gpio端口
                   比如  echo 19 > export 
                   上述操作会为19号gpio创建一个节点gpio19,此时/sys/class/gpio目录下边生成一个gpio19的目录
                   /sys/class/gpio/unexport和导出的效果相反。 
                   比如 echo 19 > unexport
                   上述操作将会移除gpio19这个节点。 
     (2) /sys/class/gpio/gpioN
           指代某个具体的gpio端口,里边有如下属性文件
          direction 表示gpio端口的方向,读取结果是in或out。该文件也可以写,写入out 时该gpio设为输出同时电平默认为低。写入low或high则不仅可以
                          设置为输出 还可以设置输出的电平。 当然如果内核不支持或者内核代码不愿意,将不会存在这个属性,比如内核调用了gpio_export(N,0)就
                           表示内核不愿意修改gpio端口方向属性 
          value      表示gpio引脚的电平,0(低电平)1(高电平),如果gpio被配置为输出,这个值是可写的,记住任何非零的值都将输出高电平, 如果某个引脚
                          能并且已经被配置为中断,则可以调用poll(2)函数监听该中断,中断触发后poll(2)函数就会返回。                      
          edge      表示中断的触发方式,edge文件有如下四个值:"none", "rising", "falling","both"。
               none表示引脚为输入,不是中断引脚
               rising表示引脚为中断输入,上升沿触发
               falling表示引脚为中断输入,下降沿触发
               both表示引脚为中断输入,边沿触发
                          这个文件节点只有在引脚被配置为输入引脚的时候才存在。 当值是none时可以通过如下方法将变为中断引脚
                          echo "both" > edge;对于是both,falling还是rising依赖具体硬件的中断的触发方式。此方法即用户态gpio转换为中断引脚的方式
          active_low 不怎么明白,也木有用过                                                                
     (3)/sys/class/gpio/gpiochipN
          gpiochipN表示的就是一个gpio_chip,用来管理和控制一组gpio端口的控制器,该目录下存在一下属性文件: 
          base   和N相同,表示控制器管理的最小的端口编号。 
          lable   诊断使用的标志(并不总是唯一的) 
          ngpio  表示控制器管理的gpio端口数量(端口范围是:N ~ N+ngpio-1) 
    四 用户态使用gpio监听中断      
    首先需要将该gpio配置为中断
    echo  "rising" > /sys/class/gpio/gpio12/edge       
    以下是伪代码
    int gpio_id;
    struct pollfd fds[1];
    gpio_fd = open("/sys/class/gpio/gpio12/value",O_RDONLY);
    if( gpio_fd == -1 )
       err_print("gpio open");
    fds[0].fd = gpio_fd;
    fds[0].events  = POLLPRI;
    ret = read(gpio_fd,buff,10);
    if( ret == -1 )
        err_print("read");
    while(1){
         ret = poll(fds,1,-1);
         if( ret == -1 )
             err_print("poll");
           if( fds[0].revents & POLLPRI){
               ret = lseek(gpio_fd,0,SEEK_SET);
               if( ret == -1 )
                   err_print("lseek");
               ret = read(gpio_fd,buff,10);
               if( ret == -1 )
                   err_print("read");
                /*此时表示已经监听到中断触发了,该干事了*/
                ...............
        }
    }
    记住使用poll()函数,设置事件监听类型为POLLPRI和POLLERR在poll()返回后,使用lseek()移动到文件开头读取新的值或者关闭它再重新打开读取新值。必须这样做否则poll函数会总是返回

    Omap3530 的GPIO中断设置:
    1.配置成GPIO,申请GPIO中断
    omap_cfg_reg(OMAP3_KBD_GPIO);配置成gpio
    if (gpio_request(OMAP3_KBD_GPIO, "kbd7279 IRQ") < 0)
    printk(KERN_ERR "Failed to request GPIO%d for kbd IRQ\n");//申请GPIO为中断引脚。
    2. 设置中断触发模式
    set_irq_type(OMAP_GPIO_IRQ(OMAP3_KBD_GPIO),IRQ_TYPE_EDGE_FALLING);
    3.使能中断
    enable_irq(gpio_to_irq(OMAP3_KBD_GPIO));
    4.申请中断
    result = request_irq(OMAP_GPIO_IRQ(OMAP3_KBD_GPIO), &Kbd7279_ISR,0, "Ds7279", NULL)


    linux提供一个模型来让驱动统一处理GPIO,即各个板卡都有实现自己的gpio_chip控制模块:request, free, input,output, get,set,irq...然后把控制模块注册到内核中,这时会改变全局gpio数组

    gpio_desc[]. 当用户请求gpio时,就会到这个数组中找到,并调用这个GPIO对应的gpio_chip的处理函数。

    gpio实现为一组可用的 gpio_chip, 由驱动传入对应 gpio的全局序号 去 request, dataout ,datain, free. 这时会调用gpio_chip中具体的实现。

    寄存器读写函数:   __raw_writel()   __raw_writeb()   __raw_readl()   __raw_readb()

    //****************linux 中 GPIO模型****************************************//

    注册方法:

    1struct gpio_chip: 表示一个gpio controller.通过这个结构抽象化所有的 GPIO源,而让板上其它的模块可以用相同的接口调用使用这些GPIO

    2: struct gpio_desc: 表示一个gpio口,含对应的 gpio_chip.

    3: ARCH_NR_GPIOS:  与板相关的GPIO口数量,即是全局GPIO数组:static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];

    4: 注册 gpio_chip时,就是根据 chip 的数据 修改全局 GPIO数组中 gpio_desc 字段(chip, flags)。

    //***********************************************//

    static struct gpio_desc gpio_desc[ARCH_NR_GPIOS]; //全局的gpio_chip 存放数组

    static struct gpio_chip twl_gpiochip = {

     .label   = "twl4030",

     .owner   = THIS_MODULE,

     .request  = twl_request,

     .free   = twl_free,

     .direction_input = twl_direction_in,

     .get   = twl_get,

     .direction_output = twl_direction_out,

     .set   = twl_set,

     .to_irq   = twl_to_irq,

     .can_sleep  = 1,

    };

    struct gpio_desc {

     struct gpio_chip *chip;

     unsigned long  flags;

    /* flag symbols are bit numbers */

    #define FLAG_REQUESTED 0

    #define FLAG_IS_OUT 1

    #define FLAG_RESERVED 2

    #define FLAG_EXPORT 3 /* protected by sysfs_lock */

    #define FLAG_SYSFS 4 /* exported via /sys/class/gpio/control */

    #ifdef CONFIG_DEBUG_FS

     const char  *label;

    #endif

    };

    struct gpio_chip {

     const char  *label;

     struct device  *dev;

     struct module  *owner;

     int   (*request)(struct gpio_chip *chip,

          unsigned offset);

     void   (*free)(struct gpio_chip *chip,

          unsigned offset);

     int   (*direction_input)(struct gpio_chip *chip,

          unsigned offset);

     int   (*get)(struct gpio_chip *chip,

          unsigned offset);

     int   (*direction_output)(struct gpio_chip *chip,

          unsigned offset, int value);

     void   (*set)(struct gpio_chip *chip,

          unsigned offset, int value);

     int   (*to_irq)(struct gpio_chip *chip,

          unsigned offset);

     void   (*dbg_show)(struct seq_file *s,

          struct gpio_chip *chip);

     int   base;

     u16   ngpio;

     unsigned  can_sleep:1;

     unsigned  exported:1;

    };



    GPIO的驱动主要就是读取GPIO口的状态,或者设置GPIO口的状态。就是这么简单,但是为了能够写好的这个驱动,在LINUX上作了一些软件上的分层。
    为了让其它驱动可以方便的操作到GPIO,在LINUX里实现了对GPIO操作的统一接口,这个接口实则上就是GPIO驱动的框架,具体的实现文件为gpiolib.c
     在配置内核的时候,我们必须使用CONFIG_GENERIC_GPIO这个宏来支持GPIO驱动。
     这里我们把目光放到gpiolib.c上,主要对外提供的接口函数,在其头文件gpio.h里可以看到:
     具体的GPIO描述符:
     struct gpio_chip {
    int (*request)(struct gpio_chip *chip, unsigned offset);
    void (*free)(struct gpio_chip *chip, unsigned offset);
    int (*direction_input)(struct gpio_chip *chip, unsignedoffset);
    int (*get)(struct gpio_chip *chip, unsigned offset);
    int (*direction_output)(struct gpio_chip *chip, unsignedoffset, int value);
    int (*set_debounce)(struct gpio_chip *chip, unsigned offset,
    unsigneddebounce);
    void (*set)(struct gpio_chip *chip, unsigned offset, int value);
    int (*to_irq)(struct gpio_chip *chip, unsigned offset);
    void (*dbg_show)(struct seq_file *s, struct gpio_chip *chip);
    int base;
    u16 ngpio;
    … ...
    };
    申请和释放GPIO资源:
    externint gpio_request(unsigned gpio, const char *label);
    externvoid gpio_free(unsigned gpio);
    设置GPIO口方向的操作:
    externint gpio_direction_input(unsigned gpio);
    externint gpio_direction_output(unsigned gpio, int value);
    设置GPIO口高低电平值操作:
    externint gpio_get_value_cansleep(unsigned gpio);
    externvoid gpio_set_value_cansleep(unsigned gpio, int value)
    externint __gpio_get_value(unsigned gpio);
    externvoid __gpio_set_value(unsigned gpio, int value);
    一全局数组,记录各个GPIO的描述符,即对应gpio_desc结构体,其中gpio_chip指向硬件层的GPIO,flags为一标志位,用来指示当前GPIO是否已经占用,当用gpio_request申请GPIO资源时,flags位就会置位,当调用gpio_free释放GPIO资源时,flags就会清零。label是一个字符串指针,用来作说明。
    在软件上,我们首先通过函数gpiochip_add注册一个gpio_chip对应的gpio_desc到全局数组gpio描述符中。其中,一个描述符对应一个GPIO,所以如果我们要使用多个GPIO,那么就在gpio_chip结构体的ngpio指定个数,base为起始的GPIO号。
    如果你想使用GPIO驱动,那么在配置内核的时候请把该驱动选上,即定义宏CONFIG_GENERIC_GPIO,然后在你的驱动里加入头文件linux/gpio.h,这样就可以用那些操作函数了。


    kernel/arch/arm/include/asm/mach/arch.h文件中

    #define MACHINE_START(_type,_name)          \

    static const struct machine_desc __mach_desc_##_type    \

     __used                         \

     __attribute__((__section__(".arch.info.init"))) = {    \

        .nr     = MACH_TYPE_##_type,        \

        .name       = _name,

        

    #define MACHINE_END             \

    };

    pxa920的板文件ttc_dkb.c的最后:

    MACHINE_START(TTC_DKB, "PXA910-based TTC_DKB Development Platform")

        .phys_io        = APB_PHYS_BASE,

        .boot_params    = 0x00000100,

        .io_pg_offst    = (APB_VIRT_BASE >> 18) & 0xfffc,

        .map_io     = pxa_map_io,

        .init_irq       = pxa910_init_irq,

        .timer          = &pxa910_timer,

        .init_machine   = ttc_dkb_init,

    MACHINE_END

    将宏展开后得到,

    static const struct machine_desc __mach_desc_TTC_DKB __used __attribute__((__section__("arch.info.init"))) = {

        .nr    = MACH_TYPE_TTC_DKB,

        .name    = "PXA910-based TTC_DKB Development Platform",

        .phys_io        = APB_PHYS_BASE,

        .boot_params    = 0x00000100,

        .io_pg_offst    = (APB_VIRT_BASE >> 18) & 0xfffc,

        .map_io     = pxa_map_io,

        .init_irq       = pxa910_init_irq,

        .timer          = &pxa910_timer,

        .init_machine   = ttc_dkb_init,

    }

    kernel/include/asm-arm/mach-types.h文件中有 “MACH_TYPE_TTC_DKB“ 的定义

    #define MACH_TYPE_TTC_DKB              2045

    同时在arch/arm/tools/mach-types文件中,也有“MACH_TYPE_TTC_DKB“的配置

      ttc_dkb           MACH_TTC_DKB     TTC_DKB            2045

    kernel boot 起来的时候,bootloader(uboot) 会传参数进来,其中包括 Machine Type,然后参考 arch/arm/tools/mach-types 并和 MACHINE_START() 第一个参数“.nr“对上号,即都为2045。因此,哪个MACHINE被初始化是在运行时由传入的参数决定的。

    MACHINE_START()的各个成员函数在不同时期被调用:

    1. .init_machine 在 arch/arm/kernel/setup.c 中被 customize_machine 调用,放在 arch_initcall() 段里面,会自动按顺序被调用 start_kernel,参考 init/main.c

    2. init_irqstart_kernel() --> init_IRQ() --> init_arch_irq() 被调用

    3. map_io 在 setup_arch() --> paging_init() --> devicemaps_init(),其他主要都在 setup_arch() 中用到

    然后我们看init_irq成员,该成员值是 pxa910_init_irq

    该函数定义如下(文件:kernel/arch/arm/mach-mmp/pxa910.c):

    void __init pxa910_init_irq(void)

    {       

        icu_init_irq();        // 初始化IRQ

        pxa910_init_gpio();        // 初始化GPIO

    }

    static void __init pxa910_init_gpio(void)

    {   

        int i;

        /* enable GPIO clock */

        __raw_writel(APBC_APBCLK | APBC_FNCLK, APBC_PXA910_GPIO);

        

        /* unmask GPIO edge detection for all 4 banks - APMASKx */

        for (i = 0; i < 4; i++)

            __raw_writel(0xffffffff, APMASK(i));

        pxa_init_gpio(IRQ_PXA910_AP_GPIO, 0, 127, NULL);

    }

    在文件 kernel/arch/arm/plat-pxa/gpio.c中,定义pxa_init_gpio函数

    void __init pxa_init_gpio(int mux_irq, int start, int end, set_wake_t fn)

    {

        struct pxa_gpio_chip *c;

        int gpio, irq;

        pxa_last_gpio = end;

        

        /* Initialize GPIO chips */

        pxa_init_gpio_chip(end);        // 初始化GPIO

        /* clear all GPIO edge detects */

        for_each_gpio_chip(gpio, c) {

            __raw_writel(0, c->regbase + GFER_OFFSET);

            __raw_writel(0, c->regbase + GRER_OFFSET);

            __raw_writel(~0,c->regbase + GEDR_OFFSET);

        }

        for (irq  = gpio_to_irq(start); irq <= gpio_to_irq(end); irq++) {

            set_irq_chip(irq, &pxa_muxed_gpio_chip);

            set_irq_handler(irq, handle_edge_irq);

            set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);

        }

        /* Install handler for GPIO>=2 edge detect interrupts */

        set_irq_chained_handler(mux_irq, pxa_gpio_demux_handler);

        pxa_muxed_gpio_chip.set_wake = fn;

    }

    pxa_init_gpio_chip函数中,GPIO chips,如下代码

    static int __init pxa_init_gpio_chip(int gpio_end)

    {

        int i, gpio, nbanks = gpio_to_bank(gpio_end) + 1;

        struct pxa_gpio_chip *chips;

            

        chips = kzalloc(nbanks * sizeof(struct pxa_gpio_chip), GFP_KERNEL);

            

        for (i = 0, gpio = 0; i < nbanks; i++, gpio += 32) {

            struct gpio_chip *c = &chips[i].chip;

        

            sprintf(chips[i].label, "gpio-%d", i);

            chips[i].regbase = (void __iomem *)GPIO_BANK(i);

            c->base  = gpio;

            c->label = chips[i].label;

            c->direction_input  = pxa_gpio_direction_input;        // 将该成员指向pxa_gpio_direction_input函数

            c->direction_output = pxa_gpio_direction_output;    // 将该成员指向pxa_gpio_direction_output函数

            c->get = pxa_gpio_get;

            c->set = pxa_gpio_set;

            /* number of GPIOs on last bank may be less than 32 */

            c->ngpio = (gpio + 31 > gpio_end) ? (gpio_end - gpio + 1) : 32;

            gpiochip_add(c);

        }

        pxa_gpio_chips = chips;

        return 0;

    }

    实际上这个注册过程也就是对gpio_chip的初始化过程,有了这个初始化,后续代码对GPIO的操作将直接调用到pxa_gpio_direction_input等函数。

    GPIO是与硬件体系密切相关的,linux提供一个模型来让驱动统一处理GPIO,即各个板都有实现自己的gpio_chip控制模块:request, free, input,output, get,set,irq...

    然后把控制模块注册到内核中,这时会改变全局gpio数组:gpio_desc[]。当用户请求gpio时,就会到这个数组中找到,并调用这个GPIO对应的gpio_chip的处理函数。

    gpio实现为一组可用的 gpio_chip, 由驱动传入对应 gpio的全局序号 去 request, dataout ,datain, free. 这时会调用gpio_chip中具体的实现。

    寄存器读写函数:   __raw_writel()   __raw_writeb()   __raw_readl()   __raw_readb() ----这些函数会在特定硬件的具体GPIO操作函数中调用,如pxa_gpio_direction_input

    gpio是一组可控件的脚,由多个寄存器同时控制。通过设置对应的寄存器可以达到设置GPIO口对应状态与功能。数据状态,输入输出方向,清零,中断(那个边沿触发), 一般是以组(bank)为一个单元,如920中分为4组。

    gpio_direction_output函数调用为例,

    该函数定义在kernel/drivers/gpio/gpiolib.c文件中,实现如下:

    int gpio_direction_output(unsigned gpio, int value)

    {

        unsigned long       flags;

        struct gpio_chip    *chip;

        struct gpio_desc    *desc = &gpio_desc[gpio];

        spin_lock_irqsave(&gpio_lock, flags);

        chip = desc->chip;            // 取出gpio_chip

        gpio -= chip->base;

        status = gpio_ensure_requested(desc, gpio);

        /* now we know the gpio is valid and chip won't vanish */

        spin_unlock_irqrestore(&gpio_lock, flags);

        might_sleep_if(extra_checks && chip->can_sleep);

        status = chip->direction_output(chip, gpio, value);        // 调用该chip的成员direction_output,注意这里的成员在开机初始化时,已经初始为pxa_gpio_direction_output函数

        if (status == 0)

            set_bit(FLAG_IS_OUT, &desc->flags);

    }




      为了给不同GPIO控制器提供一个统一的编程接口,内核提供了一个可选择的实现架构。这个架构被称作"gpiolib".在这个架构下,每个GPIO控制器被抽象成“struct gpio_chip",这里GPIO控制器的所有常规信息:

    -设定传输方向(输入/输出)的函数

    -读写GPIO值的函数

    -是否调用可睡眠的函数的flag

    -可选择的用来调试的输出(dump method)

    -用来诊断的标志

    还有一些来自device.platform_data等与体系相关的数据,比如gpio起始号和多少可用的gpio号。

      向内核注册gpio_chip是调用gpiochip_add(),注销时调用gpiochip_remove()

      通常gpio_chip是被包含在一个体系相关的结构体内,这个结构体里有一些与gpio状态相关的成员,比如如何寻址,电源管理等等。

        为了支持gpio实现框架(已经说过,是可选择的),Kconfig里应该选择ARCH_REQUIRE_GPIOLIB 或 ARCH_WANT_OPTIONAL_GPIOLIB,并且让<asm/gpio.h>包含<asm-generic/gpio.h>(只要包含就可以,直接间接无所谓)并且定义三个函数:gpio_get_value(), gpio_set_value(), and gpio_cansleep().通常还需要提供ARCH_NR_GPIOS的值(代表有几个GPIO分组)。

        上面3个函数实际上要调用底层函数,自己实现后就可以用gpio的架构了。

      #define gpio_get_value        __gpio_get_value

      #define gpio_set_value        __gpio_set_value

      #define gpio_cansleep         __gpio_cansleep

         对于SOC,与平台相关的代码为每组gpio注册一个gpio_chip,里面是参照硬件原理图和数据手册定义的。通常在平台初始化时gpio也要初始化,通过调用arch_initcall或者更早的函数,他们也可作为IRQ来用。

         还有对应外部GPIO控制器的描诉,比如I2CSPI扩展器,ASICsFPGAs等,当加载模块时,初始化函数要分配好相应的数据,然后probe()会调用gpiochip_add()注册(没详细看,具体见文档)。

    GPIO是与硬件体系密切相关的,linux提供一个模型来让驱动统一处理GPIO,即各个板卡都有实现自己的gpio_chip控制模块:request, free, input,output, get,set,irq... 然后把控制模块注册到内核中,这时会改变全局gpio数组:gpio_desc[]. 

    当用户请求gpio时,就会到这个数组中找到,并调用这个GPIO对应的gpio_chip的处理函数。

    寄存器读写函数:   __raw_writel()   __raw_writeb()   __raw_readl()   __raw_readb()

    gpio是一组可控件的脚,由多个寄存器同时控制。通过设置对应的寄存器可以达到设置GPIO口对应状态与功能。

    数据状态,输入输出方向,清零,中断(那个边沿触发), 一般是一组(bank)一组的。

    //

    注册方法:

    1struct gpio_chip: 表示一个gpio controller.通过这个结构抽象化所有的 GPIO源,而让板上其它的模块可以用相同的接口调用使用这些GPIO

    2: struct gpio_desc: 表示一个gpio口,含对应的 gpio_chip.

    3: ARCH_NR_GPIOS:  与板相关的GPIO口数量,即是全局GPIO数组:static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];

    4: 注册 gpio_chip时,就是根据 chip 的数据 修改全局 GPIO数组中 gpio_desc 字段(chip, flags)。

    gpiolib架构会在/sys/class/gpio下提供用户编程接口,可以通过这个接口去控制gpio状态。(具体见文档)

    struct gpio_desc {

    struct gpio_chip *chip;

    unsigned long flags;

    #define FLAG_REQUESTED 0

    #define FLAG_IS_OUT 1

    #define FLAG_RESERVED 2

    #define FLAG_EXPORT 3

    #define FLAG_SYSFS 4

    #ifdef CONFIG_DEBUG_FS

    const char *label;

    #endif

    };

    struct gpio_chip {

    const char *label;

    struct device *dev;

    struct module *owner;

    int (*request)(struct gpio_chip *chip,

    unsigned offset);

    void (*free)(struct gpio_chip *chip,

    unsigned offset);

    int (*direction_input)(struct gpio_chip *chip,

    unsigned offset);

    int (*get)(struct gpio_chip *chip,

    unsigned offset);

    int (*direction_output)(struct gpio_chip *chip,

    unsigned offset, int value);

    void (*set)(struct gpio_chip *chip,

    unsigned offset, int value);

    int (*to_irq)(struct gpio_chip *chip,

    unsigned offset);

    void (*dbg_show)(struct seq_file *s,

    struct gpio_chip *chip);

    int base;

    u16 ngpio;

    unsigned can_sleep:1;

    unsigned exported:1;

    };





    批量初始化方法:

    申请:

    err = gpio_request_array(leds_gpios, ARRAY_SIZE(leds_gpios));

    释放:

    gpio_free_array(leds_gpios, ARRAY_SIZE(leds_gpios));

    导出gpio到用户空间:int gpio_export(unsigned gpio, bool direction_may_change);

    创建一个sysfs连接到已导出的GPIO节点:

    int gpio_export_link(struct device *dev, const char *name, unsigned gpio)

    取消导出:void gpio_unexport();

    Gpio设置中断:

        gpio  --->  irq        int gpio_to_irq(unsigned gpio);

        首先应该设置此gpio为输入状态,然后获取对应的中断号(或错误吗)。返回编号调用:

    request_irq()free_irq()

        Irq ---> gpio        int irq_to_gpio(unsigned irq);

        返回gpio编号,再调用gpio_get_value()获取相应的值。(避免使用反向映射,不支持)

    ----------------------------------------------------------------------------------------    

    gpiolib.c gpio框架)   drivers/gpio/gpiolib.c + include/asm-generic/gpio.h [gpio chip接口]

    -----------------------------------------------------------------------------------------

    gpio_chip作为一个接口负责框架层与控制器层的通讯,主要关注点有:

    其申请/释放/方向/获取输入/设置输出/irq/base+ngpio[见第三部分控制器驱动]

    在框架层的主要关注点在:

    1. 如何分配不同chipgpio

    2. 如何管理隶属与不同chipgpio,并反向追溯到chip以调用控制器的具体寄存器操作

    3. 统一提前管理了哪些gpio状态以及是否有必要

    gpiochip_add()中是对gpio chip的注册,并插入到框架gpio的管理中,

    全局变量 static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];

    gpio_desc作为整个系统的gpio的管理者,主要包含两个成员:chip 与 flags.

    flags为框架层对gpio的整体管理标识,起MASK的作用。[有:是否已申请/是否是输出/是否保留等]

    插入的规则实现在:gpiochip_find_base(int ngpio)

    从后往前遍历全局gpio desc, 只要是非保留gpio且无宿主chip的连续gpio的空间起址作为base, ngpio则依次向下扩展。

    简单的初始化:挂上对应的chip,检查是否有设置输入函数,没有则设置 IS_OUT位到flags.

    [其中关于gpio的设备树管理暂时不予关注]

    request流程: 检查对应gpioflags是否FLAG_REQUESTED,如果未被request则调用chiprequest接口实现芯片级别的调用。

    free流程: 确认已经被REQUESTED, 后启用芯片级的free.

    从系统gpio接口传递下来的gpio均是以base为基址,而传递到芯片上都是回归到原始gpio

    ------------------------------------------------------------------------------------------

    SC8810 gpio控制器驱动

    -------------------------------------------------------------------------------------------

    这里是整个gpio系统的核心,初步总结需要关注以下几点:

    chip支持的gpio section如何划分

    gpio如何配置使能即芯片如何管理众多gpio口的多个标识位的功能选项

    申请/释放/方向/获取输入/设置输出/irq/base+ngpio的处理流程及原理

    gpio对应irq号的分配及映射规则

    配置一个系统gpio需要的必要步骤

    section:(GPIO_BASE:0xE0031000/SPRD_MISC_BASE:0xE0037000)

         {   (GPIO_BASE + 0*0x80),    0x10,    GPIO_SECTION_GPIO    },

         {   (GPIO_BASE + 1*0x80),    0x10,    GPIO_SECTION_GPIO    },

        {   (GPIO_BASE + 2*0x80),    0x10,    GPIO_SECTION_GPIO    },

        {   (GPIO_BASE + 3*0x80),    0x10,    GPIO_SECTION_GPIO    },

        {   (GPIO_BASE + 4*0x80),    0x10,    GPIO_SECTION_GPIO    },

        {   (GPIO_BASE + 5*0x80),    0x10,    GPIO_SECTION_GPIO    },

        {   (GPIO_BASE + 6*0x80),    0x10,    GPIO_SECTION_GPIO    },

        {   (GPIO_BASE + 7*0x80),    0x10,    GPIO_SECTION_GPIO    },

        {   (GPIO_BASE + 8*0x80),    0x10,    GPIO_SECTION_GPIO    },

        {   (SPRD_MISC_BASE + 0x480),   0x10, GPIO_SECTION_GPIO    },

        {   (SPRD_MISC_BASE + 0x4c0),   0xe,  GPIO_SECTION_GPIO    },

    当获取一个gpio号后,需要获取的基本信息为:在哪个section/偏移量是多大/是何种芯片gpio

    获取方法: 

    a.((gpio_id>>4) -1) * 0x80 + (u32) GPIO_BASE;

    b.gpio_id & 0xF

    c.gpio是在数字芯片上还是模拟芯片上:

    #define NR_D_DIE_GPIOS 147

    即:芯片上的gpio号小于147即位于数字芯片,否则位于模拟芯片

    在一个芯片的整个寄存器内存中,采取以section + 功能的管理方式,也就是说对于同一个gpio的不同功能配置需要通过三个步骤,第一首先

    需要找到段寄存器(section的基址);第二步是功能偏移的管理寄存器(功能偏移),第三步是根据段内偏移定位到某一位(bit)来配置。

    注:

    根据不同类型的gpio类型:

    enum gpio_section_type {

        GPIO_SECTION_GPI = 0x0,

        GPIO_SECTION_GPO,

        GPIO_SECTION_GPIO,

        GPIO_SECTION_INVALID

    };

    其相应的功能偏移页不同。

    申请:设置其功能寄存器的GPIO_DMSK功能偏移,在对应段内偏移处置位。

    释放:清除对应MASK

    输出方向:三个步骤,1.设置GPIO_DIR对应位为12.清除GPIO_INEN对应位;3.设置GPIO_DATA对应位为需要输出的值。

    输入方向:第一二步相反,无第三步。

    设置及获取值:直接设置/读取GPIO_DATA功能寄存器[需要检查一些相关的方向等信息]

    to irq

    全局的映射数组来管理所有的gpioirq的映射

    static struct gpio_irq_map gpio_irq_table[NR_GPIO_IRQS];

    映射规则是:从0-10[仅限映射10个中断号],遍历映射表找到第一个gpio offset相同的表项,返回其对应的irq值。

    方向 to gpio 一般kernel不支持使用,但实现原理与上相同。

    gpio芯片管理中,最重要的一块就是irq的管理,包括irq的所有属性管理,如:irq屏蔽使能/触发条件等等。

    下一篇笔记将详细描述 kernel irq的管理框架以及gpioirq分配规则及触发原理。

    展开全文
  • ERP系统中BOM的作用

    千次阅读 2008-03-17 12:47:00
    ERP系统中BOM的作用ERP的第一代诞生于1965年。数十年来,ERP经过长期的生产实践,吸收了许多国家先进制造业管理思想(如日本的“看板管理”等),不断发展延伸,形成了一种以生产经营、计划管理为主线,辅以CAD/CAM...
      
    
    ERP系统中BOM的作用
    ERP的第一代诞生于1965年。数十年来,ERP经过长期的生产实践,吸收了许多国家先进制造业管理思想(如日本的看板管理等),不断发展延伸,形成了一种以生产经营、计划管理为主线,辅以CAD/CAMInternetGUIEDI功能的当代企业管理方法。ERP系统中的主要功能模块,均是针对企业级资源管理而设计。
      在ERP系统中,一般包括以下模块:
      ·BOMBill of material)物料清单;
      ·PPProduction Planning)生产计划大纲;
      ·MPSMaster Production Scheduling)主生产计划;
      ·RCCP( Rough Cut Capacity Planning)粗能力需求计划;
      ·MRP(Materiel Requirements Planning)物料需求计划;
      ·CRP(Capacity Requirements Planning)能力需求计划;
      ·PAC(Production Activity Control)车间作业管理;
      ·IM(Inventory Management)库存管理;
      ·CO(Controlling)管理会计(Management Accounting)
      ·FI(Financial Accounting)财务会计(GL总帐、AR应收、AP应付);
      ·COE(Customer order Entry)客户订单输入;
      ·HR(Human Resources)人力资源计划;
      ·SD(Sales and Distribution)分销管理。
      
      在ERP系统中有一些特点:如计划的一贯性和可行性,数据的统一和共享性,灵活的决策应变性,高度的模拟预测性,物流、资金流、信息流的统一等等。这些特点表明,ERP是一个完整的经营生产管理体系,是实现制造业整体效益、提升企业全面竞争力的有效管理模式。许多经验丰富的咨询顾问谈到ERP实施体会时说,要成功实施ERP系统,三分靠技术,七分靠人才,十二分靠数据。此话虽有些夸大,但计算机只有在数据准确、完整、及时的情况下,才能发挥作用,否则只能带来错误的结果。可以说产品数据库是ERP系统运行的依据,ERP实施的广度和深度取决于其覆盖面和数据内容。因此,建立产品数据库的准备和维护工作就极为重要。
      
      一般情况下,我们将各种管理数据分为三类——静态数据、动态数据及中间数据。静态数据包括物料清单、工作中心的能力和成本参数、工艺路线、工时定额、仓库和货位代码、会计科目的设置等等;动态数据是指生产活动中发生的数据,不断发生、经常变动,如客户合同、库存记录、完工报告等;中间数据是在综合静态数据与动态数据,经过综合运算形成的各种报表,在ERP系统运行准备期及运行过程中这些数据都要定期或随时维护,保持数据的准确性。本文就静态数据中物料清单(Bill of MaterialBOM)的作用,结合CAD(Computer Aided Design,计算机辅助设计)CAPP(Computer Aided Process Planning,计算机辅助工艺编制)PDM(Products Data Management,产品数据管理)等系统作详细的描述。
      
      物料清单(BOM),又称为产品结构表或产品结构树;在某些工业领域,可能称为配方要素表或其它名称。在ERP系统中,物料一词有着广泛的含义,它是所有产品、半成品、在制品、原材料、配套件、协作件、易耗品等等与生产有关的物料的统称。BOM作用于计算机识别物料、接受客户定单、编制计划、配套(装配)和领料、加工过程跟踪、采购和外协、成本计算、报价参考、物料追溯、改进产品设计等等。系统可以采用多种方法描述物料清单,如单层法、缩进法、模块法、暂停法、矩阵法以及成本法等等。
      
      1. CAD(Computer Aided Design)中的BOM
      设计部门既是BOM的设计者,又是BOM的使用者。单一零件诸如图号、物料名称(材料类型如45号钢)、重量、体积、设计修改审核号、物料生效日期等各种信息;组件或部件还包括外协件、外购件、通用件、标准件、借用件、各单一零件装配数量、部件图号等信息;总图(由零件、组件部件等装配而成)还包括包装、装件清单、技术文件、产品说明书、保修单等等信息,这些都是BOM信息的组成部分。在设计部门(CAD)中,通常所说的BOM实际上是零件明细表,是一种技术文件,偏重于产品信息汇总。
      
      设计部门按某种类型产品的图号来组织BOM信息。设计部门在接到定单后按照定单的要求,一般情况下有三种设计思路——自顶向下形式设计、自底向上形式设计、由中间向两头形式设计。无论那一种设计方式,在图号的组织上都是一致的,都是按照图号来合并产品信息,形成该产品的总明细表、标准件汇总表、外购件汇总表、外协件汇总表等,在需要的时候还能生成产品图纸目录(满足没有运行ERP系统的客户或外协工厂)。有时一个相同的零件由于属于不同的产品,也就有了不同的图号,因此不一定考虑企业物料编码的唯一性。需要说明的是,在形成物料清单后,每一种物料都有唯一的编码,即物料号。不要将零件明细表(CAD通称为BOM表)与ERP中的BOM信息混淆。设计部门中的零件明细信息表转化为ERP系统中的BOM信息,需要设计部门、工艺部门和生产部门的共同协作,以及PDM(产品数据管理)设计产品关系特性的管理来解决零件明细清单与BOM表之间的异同信息,特别是图号与编码号不一致方面(PDM产品结构模块通过其规则库、变量和零件表等功能来完成)。
      
      就使用而言,无论何时,当产品结构发生变化,或者客户更改技术文件、涉及质量问题或对某个零件进行重新改进设计时,为确保物料清单的准确性,都必须以设计变更通知为依据。在设计变更通知文件的指导下,设计部门通过BOM信息表中获取所有零件的信息及其相互间的结构信息。只有得到这些信息,才能对其进行定义、描述或修改,从而使生产能正常地运行下去(特别是客户的紧急更改通知)。根据设计变更通知编号,在PDM支持下,可以方便地检索变更信息,指导生产、装运和售后服务等生产活动。
      
      在实际生产运行过程中,设计变更是导致数据不准确的重要因素,因此一定要有一套行之有效的设计变更通知管理方法来管理设计变更通知。由于要涉及销售、采购、生产、工程技术、财务等部门,因此一般由企业的副总直接管理设计变更通知。这一过程须经过设计变更通知确认、分析、审批、文件和监督五个步骤。
      
      设计部门(CAD)产生的部分数据经PDM处理后传输给ERP系统。
      
      2. CAPP(Computer Aided Process Planning)中的BOM
      产品经过设计部门设计完毕后,部分电子数据转交工艺部门制订工艺路线(CAPP),成为说明零部件加工或装配过程的文件。它不是技术文件,而是计划文件或指导生产文件。CAPP一般由工艺过程卡、加工工序卡、锻铸热处理卡、检测卡、工装材料工时等汇总信息组成;在一张加工工序卡中由工序(加工步骤)、工时定额(占用工作中心的负荷时间)、加工设备、检测设备、加工工具、工装夹具、材料等组成。
      
      在编制工艺计划时,除涉及设计的每一个细微之处外,同时还要涉及BOM中的主工作中心物料、材料物料、加工夹具物料、工装物料及辅料物料等。维护这些静态数据的准确性是保证生产按计划进行的前提。主工作中心的设备维护、备件管理、维修记录,材料采购与库存变化情况,加工夹具、工装设备、辅料等变化都要实时反映到工艺计划编制中去。在工艺计划编制过程中,要能随时(面向对象)地浏览BOM信息,输入BOM信息,报警BOM信息(工艺编制人员发现错误报警),实时反映更新的BOM信息等等。
      
      在没有ERP系统支持的情况下,对工艺编制人员就要提出很高的要求:不仅要求其熟知零件加工过程和加工设备的现行状况,还要知道技术参数、库存情况、加工夹具、工装设备等情况。一般情况下,企业培养一个类似人员需要20年时间。现在,工艺人员在ERP系统的支持下,可以方便地查询按BOM结构设计的典型工艺数据库、获取设计信息、查询机床设备等技术参数等,也能很容易地编制CAPP,保证工艺文档的完整性、一致性、正确性和执行可行性。
      
      工艺部门(CAPP)产生的数据经PDM处理后传输给ERP系统。
      
      3. PDM(Products Data Management)中的BOM
      PDM实际上是连接CAD/CAPPERP的核心模块,它管理与产品相关的信息(ERP过程(CAD/CAPP技术,起着由过程(CAD/CAPP技术向信息(ERP转化,信息(ERP过程(CAD/CAPP技术转化的重要中间过程,形成了双向的无缝传输数据,避免了大量重合数据的产生。
      
      由于不同部门有不同形式的BOM信息,企业经常要花费大量的人力和时间才能完成这些报表,而且还要不断维护BOM的一致性,避免产生严重的MRP运算错误。四川电器股份有限公司原来采用的手工录入方式,录入从CAD部门中统计的BOM信息,一个熟练的录入人员录入一个产品的BOM数据需要一周左右的时间,还不包括录入错误导致的返工时间和造成了恶劣影响;现在通过PDM,为ERP系统自动传输BOM数据,只需要几分钟时间。
      
      在产品整个生命周期,PDM以数据仓库(所有系统可共用一个数据库)为底层支持,以材料清单(BOM)为其组织核心,把定义最终产品的所有工程数据和文档联系起来,实现产品数据的组织和管理,诸如产品配制管理、图文档管理、工作流程管理、设计变更管理、权限(角色)管理、版本管理、项目管理、维修记录以及日志管理等等。
      
      PDM系统根据各自的功能特点与可解决工程问题的不同,分为三大类,即:以文档、数据管理为重点的;以设计过程及产品结构管理为主面向CAD的;面向硬、软件异构系统集成平台的。其中第二类与BOM信息最为密切,经过转化处理,达到ERP所需要的BOM信息。今天,大多数流行的PDM系统都能与SAP R/3集成,Baan还有自己的PDM产品,有效促进了ERP系统中生产、设计、采购和销售等各个部门的沟通与交流。
      
      4. ERP(Enterprise Resource Planning)中的BOM
      除了前面所描述的系统与BOM有关外,生产部门、产品成本核算部门、物料需求计划(MRP)系统、销售部门也有很大关系,生产部门使用BOM来决定零件或最终产品的制造方法,决定领取的物料清单;产品成本核算部门利用BOM中每个自制件或外购件的当前成本来确定最终产品的成本和对产品成本维护,有利于公司业务的报价与成本分析;物料需求计划(MRP)系统中BOMMRP的主要输入信息之一,它利用BOM决定主生产计划项目时,动态确定物料净需求量,知道需要哪些自制件和外购件,需要多少,何时需要,标准用料与实际用料的差异分析;销售部门通过Internet访问数据源,可以方便地报价,提供准确的零件设计信息与追踪制造流程等自助服务,客户还可以自己下定单购买产品备件。
      
      通过BOM信息,还可以方便地考核各部门的业绩,可以方便地抽取信息进行统计与分析;如果有了新的BOM资料需求,还可以利用原来的BOM资料构造新的BOM资料,简化近似BOM资料的编制工作;如果对BOM信息深入研究,还可以通过不同的产品BOM资料来研究其它产品BOM资料的错误检查,以免计算机输入或认为修改带来的错误,将错误率降到最低。
      
      BOM是任何管理系统中的基础,它几乎与企业中的所有职能部门都有关系,如果没有BOM,就无法制造出同样的产品,直接影响到系统的处理性能和使用效果。为此,要想提高生产管理系统的效率,BOM准正确与否是十分重要的。尽管数据已经非常准确,但也不要忽视人的重要性,对于特殊变化,利用手工在系统中对BOM信息的内容进行增加、删除和修改等编辑工作,可以顺利完成任务。
      
      总之,通过建立企业信息(ERP管理和过程(CAD/CAPP技术两条主线,以BOM为信息纽带,以PDM为核心,再结合CAD/CAPPERP系统,辅以InternetEDI系统,就可以真正达到企业信息化建设的目标。
     
    展开全文
  • OA系统

    千次阅读 2017-09-06 17:34:23
    办公自动化(OA)是面向组织的日常运作和管理,员工及管理者使用频率最高的应用系统,自1985年国内召开第一次办公自动化规划会议以来,OA在应用内容的深度与广度、IT技术运用等方面都有了新的变化和发展,并成为组织...
    办公自动化(OA)是面向组织的日常运作和管理,员工及管理者使用频率最高的应用系统,自1985年国内召开第一次办公自动化规划会议以来,OA在应用内容的深度与广度、IT技术运用等方面都有了新的变化和发展,并成为组织不可缺的核心应用系统。主要推行一种无纸化办公模式。
    

    OA系统技术平台

    OA系统的英文全称是:Office Automation System ,意为办公自动化系统。
    随着OA应用内容的不断扩展,OA技术也在不断发展,从过去的BASIC+文件系统到VB+ACCESS、DELPHI+ORACLE、PHP+mysql、JAVA+mysql,基本形成了三大主流技术:
    1. .net+关系型数据库(RDB)技术
    基于.net+RDB的办公平台则以简单、灵活、易用的特点获得了广泛的市场。.net是微软的企业级应用开发平台,优势是组件众多,可以搭建个性化的应用。但是由于需要比较专业的开发技术,并且系统的开放性较差,对微软以外的平台兼容性不好,以及版权的原因,所以在绝大多数企业看来并不适用,只有少数外企愿意使用这个技术。[1] 
    2. SUN的JAVA+RDB技术
    JAVA(J2EE标准)以其开放性、与平台无关性引领着技术发展方向,并迅速在各类应用系统中得到广泛应用与推广,在OA领域市场领域不断扩大。
    JAVA技术的优势是跨平台、安全稳定、开放性好,尤其是开放性这个特点,使得JAVA顺应时代发展需求,成为OA软件开发商的宠儿,截至2014年,市场上主流的OA软件中,90%以上采用JAVA技术。华天动力OA软件是业内第一个向用户提出需要关注开发技术的厂商,但这个观点起初并不为广大用户甚至个别厂商所认同。
    市场是客观的,它会根据实际需要做出正确的选择,随着用户意识到软件开放性、拓展性、兼容性的重要性,JAVA很快成为企业级管理软件开发的最主要工具。原本一些采用其他技术的厂商,也纷纷加入到JAVA阵营,没有及时转型的,则逐渐被市场淘汰了。[1] 
    3.IBM Lotus Domino技术
    Lotus自1989年推出,以电子邮件、协同、非结构文档处理、安全机制见长。然而随着OA应用的内涵不断丰富,Domino也暴露出一些明显的弱点,不妨将技术原理相同的.net/JAVA与Domino作一简单的比较(以OA应用为前提):
    .net/JAVA更类似3GL工具,应用功能的实现需要更多的开发或集成,应用的成熟需要不断的进行功能沉淀与积累;而Domino更像4GL工具,提供了业界领先的协同工具、企业级文档处理、文档级安全控制机制、大量的应用模板,使其更擅长办公应用支撑,但面对大量结构化业务信息处理时则显得明显不足。
    IBM是OA软件的鼻祖,因此它的Domino技术也在十几年前被视为是OA软件的正统平台,其优势是安全性和稳定性极佳,底层架构很健壮。
    但它有两个致命的缺陷,一是开放性很差,很难和其他管理系统进行数据整合;二是技术太复杂,实施、维护、二次开发都需要专业的技术人员来做,导致相关的成本非常高。这两个缺陷导致Domino技术很难适应中国企业的需求。[1] 
    4.Suo-基于saasj2ee服务
    OA将业务流程与审批流程真正的做到了根据需求而变化的流程自动化平台。擅长业务流程及审批流程,最注重与第三方ERP的集成工作,实现目标是将企业的审批流与业务流全部打通,最终形成报表体系,服务于决策。
      当前上海索昂软件SUOOA中国区系统已经实现了很"Duang"的[2]  免费版OA系统[3]  ,面向所有企业。

      
    oa系统界面 oa系统界面

    OA系统高端技术

    OA系统OA品牌

    OA系统的主流技术,从过去的Domino逐步向NETJava迁移,主流的软件公司已经将JAVA作为根本技术路线,而原有Domino、.NET路线的产品,在高端用户需求面前,逐步成为明日黄花,此类公司也在悄然转型,沿着JAVA路线开辟新产品。

    OA系统平台化能力

    OA的通用功能相对成熟的情况下,随着客户管理应用的深入,更多的OA和ERP的边缘需求,开始旺盛出来,因此如何持续的满足客户的功能需求,成为拉长产品生命周期的重要因素。如何通过无码开发实现快捷的功能定制成为平台化产品的发展方向。

    OA系统系统集成

    对于信息化起步阶段的用户来讲,单系统应用就足够了,但对于已经有相当信息化基础的高端OA用户来讲,如何与ERP系统进行数据集成、信息集成、门户集成,如何与HR系统进行组织集成、用户集成,如何与即时消息进行消息集成,甚至如何进行数据拆分和重建等成为思考的因素之一。
    但集成是把双刃剑,缺少标准接口而完全定制开发的集成,又可能给升级、系统性能、项目周期等带来风险和隐患。因此考察OA系统时,重点研讨标准接口能力和产品化机制成为要点。基于数据持久层和SOA标准服务接口的集成方案,成为国内大部分用户和产品首选技术方案。

    OA系统移动应用

    随着手机操作系统和CPU的持续升级、随着wifi无线的普及和手机带宽的持续扩容,通过手机、平板电脑实现随时随地的办公,已经成为可能,尤其是高层管理者,通过移动应用实现对时间碎片的高效应用成为关注点。
    移动OA系统,不仅能够在手机上操作办公OA的功能,而且能够处理ERPCRM等业务数据,成为国内最具竞争力的办公、业务一体化移动OA软件。

    OA系统企业号OA系统应用

    企业号推出后,各大厂商将自己的OA系统与微信企业号对接,企业用户也多选择了此类的OA系统,微信企业号的优势在于:[4] 

    1. 企业内部具备沟通的简易化。

    2. 流程审批实现了语音化,目前,OA系统就已经实现了流程审批的语音识别操作,这种技术不仅可以具备高效的真实性核对,E38.wang更加可以通过语音的方式明确批注的详细解释。让流程审核更具精准性。

    3. 移动集成化,以往的OA中,移动仅限OA系统本身,也可以和其它的ERP、CRM高度集成,实现在微信端的企业信息共享,这将改变OA系统单一存在的局面.





    OA系统流程互访

    OA系统在实际的应用中,尤其是中大型单位,用户最需要的,就是将业务流程和审批流程打通。甚至是企业的审批流程可以调用业务流程。这将是一个中大型企业实现信息化的高级基础。
    BPM流程功能,不仅能够很好地实现公文、请示汇报等OA应用,而且能够实现供销合同、借款报销,甚至生产制造等流程处理,成为国内第一套流程型协同软件,也由此被赛迪、中国电子商务协同、新浪等机构和媒体广泛评价为产品竞争力第一。

    OA系统OA设计

    1. “可行性和适应性”
    所谓可行性,是指需求提炼时,应该吻合适合核心需要,满足主要功能,而不是超越当前技术水平放卫星!
    所谓适应性,是指产品的实施条件和应用条件,要吻合企业当前的环境,超越环境搞亩产万斤粮的大跃进是注定要失败的。
    核心需求的吻合度,是OA价值兑现的保障,用户在工作流程、公文管理等方面的核心需求的满足度是项目成功的基础。
    2. “前瞻性和实用性”
    OA系统的开发设计,即要考虑到最大限度的增加系统的价值,最大限度的吻合各应用者的需求,充分考虑系统今后功能扩展、应用扩展、集成扩展多层面的延伸,实施过程应始终贯彻面向应用,围绕应用,依靠应用部门,注重实效的方针。同时又要兼顾到成本控制、项目周期控制等因素,因此在功能的部署上也需要遵循实用主义。
    3. “先进性和成熟性”
    先进的管理理念、技术和方法,可以提升企业的竞争力,延长系统的生命周期,但同时,任何创新都意味着小白兔实验,风险较大,因此又要注意软件系统、硬件设备、开发工具、软件产品的是否成熟,在先进性和成熟性之间找到平衡点,成为价值最大化的关键。
    4. “开放性和标准性”
    数据孤岛、信息孤岛、应用孤岛,已经成为多年信息化建设后的后遗症,而解决这些孤岛的关键因素在于开放,解决这些孤岛的效率取决于标准化。
    如同我们的插座和插头的关系、如同我们的外设和usb口的关系,OA系统是否足够开放和标准化,成为架构设计时首要考虑的问题。
    在当前和未来,OA系统需要轻松与各种操作系统、中间件、数据库、业务系统及工具软件进行平滑对接,当前主流的厂商都在这方面做了充分的考量。
    5. “可靠性和稳定性”
    OA系统里流转了大量的管理数据,因此必须是可靠的,一般的人为和外部的异常事件不应该引起系统的崩溃;当系统出现问题后能在较短的时间内恢复,而且系统的数据是完整的,不会引起数据的不一致。
    我们曾对OA系统组织过压力测试,在负载均衡的情况下,3000人同时在线时,系统登陆(包括整个主界面加载)不大于8秒。数据浏览不大于8秒;数据查询不大于8秒;数据统计不大于15秒。
    我们还对OA产品,运行在IBM服务器上的稳定性做过测试,基本上可以做到以下几点:平均无故障运行时间:大于10000小时;可用率:系统总体平均可用率在99.99%以上;稳定性:主机系统能够保持7*24稳定的不间断运行
    6. “安全性和保密性”
    OA系统的开发设计既考虑信息资源的充分共享,更要注意信息的保护和隔离,因此系统应分别针对不同的应用、不同的网络通信环境和不同的存储设备,采取不同的措施,包括系统安全机制、数据存取的权限控制等以确保系统的安全性。
    其中,采取的措施包括但不限于以下
    平台安全:架构设计考虑安全性要求,平台软件达到安全设计标准。
    应用安全:权限控制、支持身份认证接口、防篡改、防暴力破解等措施完善,并且可以跟USBkey、CA、IP地址限制等各种安全措施进行方案组合。
    数据安全:支持文档安全软件整合技术,从而做到数据传输加密、远程安全访问、数据存储加密,并且可以VPN等各种安全方式进行绑定,支持入侵检测与防御系统、防火墙的应用。
    容灾备份:支持各种容灾的软硬件设备的使用等。
    OA产品的安全策略 OA产品的安全策略
    管理安全:提供完善的日志功能,能够记录系统使用人员的关键操作,保证系统应用的安全
    密码策略:初始密码强制更改、启用图形验证码、支持USBkey接口、密码过期控制、密码错误次数控制、密码强度设置等,从而防止暴力破解和恶意攻击。
    系统网卡MAC和IP的绑定;支持CA认证、数字签名加密技术;支持电子钥匙(Ukey)技术和指纹Ukey技术;支持安全套接(SSL)技术;软件系统严密、灵活的访问安全控制,功能授权与数据范围授权结合;
    系统有整体的用户/权限管理体系,可统一进行用户/权限的管理,实现到字段级的查询、修改、管理权限控制;系统提供用户认证、数据传输、数据存储、数字签名等安全手段接口,可在各个环节提供对第三方安全认证系统的支持。

    OA系统失败原因

    OA行业,有句名言,成也萧何,败也萧何,多少CIO折戟OA系统的建设!
    这句话的意思是,因为OA系统是全员信息化的第一台阶,所以如果失败了,上到老板,下到普通员工,都会知道此事是谁牵头做的!因此,对CIO或者信息主管的职业前程影响很大。
    笔者在OA行业潜心研究过十余年,十余年来,笔者见证了OA行业的起落沉浮、技术的变迁、品牌的更迭,同时也耳闻目睹、道听途说了很多客户成为OA建设的失败案例。总体而言,选择有竞争力的产品,是减少选型风险的重要因素。
    吃人嘴软、拿人手短
    与很多楼脆脆、桥塌塌之类的烂尾工程一样,如果决策者或者有影响力的个人,在项目中掺杂了太多的个人利益,其结果会是不但选错了产品和供应商,而且实施过程中吃人嘴软、拿人手短,无法以单位利益最大化为目标,争取资源、控制风险,最终会导致OA系统项目的失败!
    指鹿为马、选马成驴
    部分客户因为缺少信息化的经验,无法清晰的区分OA、PM、KM、ERP等不同管理软件的定位,因此在选型过程中,经常会出现让OA公司开发业务系统、让业务软件厂商开发OA模块、甚至是拿个即时通讯工具当成OA的全部的现象,这种定位上的错误,导致的结果是原本计划是买匹宝马,最终却牵回了一头驴子!
    崇洋媚外、盲从品牌
    选错OA供应商,往往是失败的头等杀手,如同婚姻一样,女怕嫁错郎、男怕选错行。在选择嫁入豪门之前,先想清楚自己到底需要什么?按照经济学理论,越是价格昂贵、技术复杂的商品,在决策中,品牌的参考价值越少!正确的婚姻观是,选择爱你的人,而不是你爱的人。
    请记住:登记只是婚姻的开始,嫁入豪门的话,除了你付出对等的银子外,请你确认在后续服务中可以忍受得住寂寞!
    路线错了、南辕北辙
    OA技术路线的正确选择,对实施成功和系统的生命周期,起决定性作用。
    历经二十年的变迁,OA技术路线,从Domino、Php、.Net一路走,当今JAVA语言、J2EE已经成为高端产品的首选路线,原因是JAVA具有很好的开放性、兼容性、跨平台性、扩展性等。很多公司都以JAVA作为主要武器,攻城略地开发产品。
    很多选错路线的OA公司,近几年纷纷开始启动JAVA产品线,也验证了这一点。
    自身混乱、上了白上
    在实际的应用案例中,很多企业自身的流程规划并不清楚,但管理者非常希望上OA,或者说某个职能人员非常想上,于是便上了。实际实施才发现,原来企业自己都不知道应该怎么做。最后还是不了了之。

    OA系统OA选型

    结合十几年来服务成千上万的客户的经验,提出了在OA选型时需要关注的五点:

    OA系统关注本质

    选型小组的负责人要深刻认识到OA软件是全员信息化,其本质是全员工作模式和企业管理模式的一次变革,在选型过程中,可能涉及到较多的部门,在未来的应用推广过程中,一定是一把手工程,因此要基于全员去安排需求调研、产品选型、应用推广和集成整合等工作安排和工作节奏。
    OA的本质是流程与知识、门户管理,其中流程是其核心本质。而OA项目的成败本质与其他管理软件一样,都在于产品,再好的品牌和顾问,如果产品缺乏竞争力,实施的效果也大打折扣。

    OA系统核心价值

    OA软件的应用范畴涉及到所有知识性员工,是全员信息化的第一台阶。既然是全员应用,提高全员的工作效率而不是某个部门、某个领导、某个人的效率,成为OA软件的核心价值定位,如果以通用性和经常性两个纬度对OA软件的功能进行筛选的话,OA软件的核心应用是:流程审批、协同工作、公文管理(国企和政府机关)、沟通工具、文档管理、信息中心、电子论坛、计划管理、项目管理、任务管理、会议管理、关联人员、系统集成、门户定制、通讯录、工作便签、问卷调查、常用工具(计算机、万年历等)。如果以给企业带来价值大小的纬度对所有功能模块评估的话,基本上遵循80:20定律,其中的工作流程模块、协同工作模块、项目管理模块为企业带来的了80%的价值,而其余的模块带来20%的价值。
    因此抓大放小,重点考察工作流程、协同工作、项目管理的技术水准,成为产品选型的不二选择。

    OA系统厂商态度

    OA软件是涉及到全员使用的管理软件,因此采购OA软件与采购财务软件、硬件设备不同,不是简单的、一次性的买卖交易,而是持续一年甚至多年的管理变革、咨询和人工服务,在这个过程中,需要持续不断的与厂商互动,来解决实施、应用、推广过程中遇到的各种需求变更、BUG处理、功能调整、软件集成等等问题。
    在持续出现的、纷繁复杂的问题面前,客户如果面对的只是一个普通销售人员或者普通实施人员,是无法调动整个组织进行资源协调和支持的,因此选择产品的同时,也要重点考察OA厂商的态度,判断他们是否重视你,选择一个爱你的人,远比选择一个你爱的人重要。
    而管理软件领域内,众所周知的潜规则是:领导只参与和出席公司认为重要的项目选型,领导的级别越好,说明对你越重视,也意味着过程中遇到问题后,资源协调到位的可能性越大。

    OA系统成本控制

    很多客户的CIO或者信息化负责人,在评估OA软件的成本是,只是简单的考虑到公司在OA软件项目上的预算和供应商的报价,而忽视了OA软件部署的软硬件环境成本、软件实施过程中参与人员的时间成本、培训成本、将来的维护难度和服务成本、将来数据集成的开发成本、升级成本和数据迁移成本等等,因此往往陷入“捡了芝麻、丢了西瓜”的尴尬境地。
    当然管理软件的选型与硬件设备的选型不同,硬件设备的选型成本占有最重要要素,而管理软件的选型,软件应用后的价值占有最重要要素。
    关于OA软件选型过程中的采购成本,行业内的潜规则是:去掉最便宜的,去掉最贵的,选择综合成本受控的厂商。选择产品竞争力强的产品,是最有效的成本控制措施,盲目追求低成本而选择低价产品,和盲目追求高价格以寻求心理保障,都是不可取的。

    OA系统售后服务

    OA办公系统的服务是软件深入、持久和稳定使用的保障。首先,软件服务的效率和质量直接影响软件正常稳定的运行和用户的满意度。其次,OA办公系统服务的成本太高,用户最终也难以承受。OA办公系统的服务是一个系统的工程,做好了不仅能有力保障产品的成功应用,还能降低用户的使用成本。
    而且,OA办公系统服务是OA厂商的软件产品、团队的技术实力和项目经验、项目的管理水平等多方面的综合因素的体现。

    OA系统大型企业如何选型

    1、做好项目规划和管理,严格控制项目目标、项目实施、项目成本、验收标准、后续服务等内容。
    2、应该选择专业化的OA办公系统厂商,尽量避免其他软件开发商或系统集成商。因为OA办公系统不是这些公司的主业,他们通常采用项目定制的方式来实施,会导致实施周期长、成本高,稳定性和适用性差,后期的服务和二次开发都非常不便,后续费用也很高。
    3、系统应该非常成熟、健壮、稳定,能够满足大规模用户同时在线使用,并且访问速度快。系统的实施应该简单,对于标准化的功能,可以快速实施,并简单培训后就可以使用,以降低实施周期和成本。
    4、系统应该具有门户的特点,满足企业的多层级使用,同时具有严密的权限设置和安全防护措施,让不同级别的用户可以安全使用。系统应该具有很强的流程管理功能,满足企业流程规范和流程优化的需求。
    5、系统需要具有平台化的特点,采用多层架构,具有很强的开放性和灵活性。因为大型企业通常掌握一定的软件开发技术,因此OA办公系统最好是能够提供一个开放性的平台,让IT人员利用这个平台搭建个性化的应用系统,并实现和其他系统的数据整合。类似于华天动力的自定开发平台就具有这个特点。
    最后需注意,大型企业在选购OA办公系统时往往有着更大的空间,但其实也有着更大的风险,对OA办公系统这种看起来简单,实际却并不简单的管理系统来说,还需要选型者认真、理性的对待,这才是项目成功的第一步。[5] 

    OA系统实施指南

    高效的项目实施团队
    高效的项目实施团队是OA项目实现成功推广的重要保障。项
    OA系统实施成功三要素 OA系统实施成功三要素
    目经理不仅要有过硬的软件技术,在项目咨询、项目管理上也要精通,业务上能够深刻领会客户的实际需求,在监管项目质量、项目进度、带领团队上游刃有余,并且能有效结合产品和管理方式,
    项目团队只有指引客户把现有功能熟练运用,帮助客户实现价值最大化,才是真正实现管理落地。项目经理的重要任务就是使最普通的用户也能得到满足需求的、美观易用的、灵活的功能应用,实现系统应用效果的最大化。
    完善的实施工具
    协同OA系统的实施不是三天五天就能解决的问题,在OA系统实施上工作内容繁杂、涉及到众多跨系统的业务系统集成、员工的IT素质参差不齐,要想全面提高项目的实施率,必须借助先进的工具。
    在协同OA系统的实施过程中,首先会采用项目管理软件,对OA系统的整个项目进行阶段划分、人员分工、任务分解、文档共享、过程协作和资源调度,并且通过OA系统双系统运作的方式,为客户提供更快的融入和学习支撑,还提供专家对项目中的问题进行专家诊断和问题排除,提供各种专业的电子工具,对过程进行质量控制,顺利提升提高实施的效率。
    标准化实施方法论
    标准化实施管理方案是以项目管理理论作支撑,对项目过程进行标准化质量控制。严格参照成熟的项目管理体系,对项目进程进行全过程管控,将项目划分成7个关键阶段,每个阶段拆分成5-8条核心任务,每项目任务责任到人、设定完成和交付标准,并且通过过程文档交付和检查,对项目进行多维度质量管控,保证最终上线的可靠性、可用性、可交付性,大大提高用户的满意度。

    OA系统设计原则

    随着信息化建设受到越来越多企业的重视,OA办公系统的应用也日渐普及起来。同时,用户对OA办公系统的要求也在不断提升。如何让OA办公系统满足广大用户的需求,这是一个需要所有厂商共同思考的问题。OA办公系统的设计原则由此也愈受关注。国内OA办公系统厂商也在多年成功实践中总结出来了OA办公系统的九大设计原则:
    1.整体性
    系统整体设计能有效的实现后台一体化管理,前端满足用户个性化需求,系统标准化程度高。
    2.先进性
    软件采用的技术,将在相当长的时间内保证技术的发展能力,应具有良好便捷的升级能力,选用的硬件设备及操作系统、数据库产品、应用软件均具有先进性及成熟的技术与产品。
    3.规范性
    遵循统一的国家规范公文格式和交换接口标准。
    4.高效性
    系统提供对各类事务处理的高效性。使对大容量数据的查询和更新等操作也在较短的时间内迅速完成。对于大数据量的处理,也能高效地完成。
    5.安全可靠性
    采用最成熟和应用最广泛的技术平台,支持身份认证技术、安全加密技术;数据在传输过程和数据库中采用高加密技术,保证数据的安全性。分不同的角色控制信息数据,采用横向和纵向结合的矩阵权限控制模式,保证企业的各种信息安全。
    6.扩展性
    由于计算机和网络等领域技术发展十分迅速,应用环境,系统硬件及系统软件都会不可避免将被更新,系统的可扩充性及版本的兼容性,直接影响着应用系统和用户需求的发展和功能的提升。因此,OA系统十分重视扩展性,能很容易地适应调整,扩充和删减;另一方面,它还具有与其它系统的接口能力,利用各系统功能之长,进行优势互补。
    7.适应性和灵活性
    在日常工作中,不可避免地需要进行机构及人员的调整,OA系统能提供充分的变更与扩展能力,适用机构及人员的调整。OA系统还具有图形化工作流定义工具,系统管理员可在浏览器环境下任意调整或定义工作流程。系统具有灵活的信息发布系统,用户可根据需要定制发布需要的新闻、通知。
    8.易用性
    系统的设计尤其重视用户界面的友好性。简洁大方、功能齐备、美观实用、提示准确。
    9.健壮性
    OA办公系统的开发设计应该支持应用和数据库等多重负载均衡能力,支持附件服务器和数据库服务器分离技术,从而支持数万用户同时在线和同时操作的能力,不会因为用户数的增长或者信息量的增长,而导致系统响应能力下降。
    如何分辨OA办公系统的优劣?设计原则是一个重要的评判标准。优秀的设计原则能让OA办公系统脱颖而出,实现系统整体的高性能、高可用、可扩展,发挥出更强的协同办公能力。而OA办公系统设计的九大原则,基本上包括了OA办公系统的性能指标,给项目开发和用户选型提供了有益的借鉴,对于中国OA办公系统的整体水平也能起到提升作用。

    OA系统系统历史

    OA系统起步阶段

    (1985年―1993年):是以结构化数据处理为中心,基于文件系统或关系型数据库系统,使日常办公也开始运用IT技术,提高了文件等资料管理水平。这一阶段实现了基本的办公数据管理(如文件管理、档案管理等),但普遍缺乏办公过程中最需要的协作支持、文档资料的综合处理等,导致应用效果不佳。

    OA系统应用阶段

    (1993年-2002年):随着组织规模的不断扩大,组织越来越希望能够打破时间、地域的限制,提高整个组织的运营效率,同时网络技术的迅速发展也促进了软件技术发生巨大变化,为OA的应用提供了基础保证,这个阶段OA的主要特点是以网络为基础、以工作流为中心,提供了文档管理、电子邮件、目录服务、群组协同等基础支持,实现了公文流转、流程审批、会议管理、制度管理等众多实用的功能,极大地方便了员工工作,规范了组织管理、提高了运营效率。

    OA系统发展阶段

    OA应用软件经过多年的发展已经趋向成熟,功能也由原先的行政办公信息服务,逐步扩大延伸到组织内部的各项管理活动环节,成为组织运营信息化的一个重要组织部分。同时市场和竞争环境的快速变化,使得办公应用软件应具有更高更多的内涵,客户将更关注如何方便、快捷地实现内部各级组织、各部门以及人员之间的协同、内外部各种资源的有效组合、为员工提供高效的协作工作平台。

    OA系统晋升阶段

    移动互联网的高速发展,越来越多的应用都转移到移动智能终端中,人们也渐渐习惯在智能终端上工作,企业迫切需要一套随时、随地随手使用的办公自动化系统,使得企业的管理员、业务人员不管置身何地,都可以随心随意地协同办公。于是移动OA出现也成为了必然,成为OA应用软件的发展的方向。

    OA系统优势

    OA系统自动化

    在手工办公的情况下文档的检索存在非常大的难度。OA办公自动化系统使各种文档实现电子化,通过电子文件柜的形式实现文档的保管,按权限进行使用和共享。企业实现OA办公自动化系统以后,比如说,某个单位来了一个新员工,只要管理员给他注册一个身份文件,一个口令,他自己上网就能看到符合她身份权限范围内的企业内部积累下来的各种知识,这样就减少了很多培训环节。

    OA系统协同办公

    OA系统是支持多分支机构、跨地域的办公模式以及移动办公的。如今来讲,地域分布越来越广,移动办公和协同办公成为很迫切的一种需求,如果你将文件保存在网盘或同步盘中,你就能随时随地查看文件,使相关的人员能够有效地获得整体的信息,提高整体的反应速度和决策能力。

    OA系统基本框架

    实现数字化办公既不同于传统的OA,也不同于MIS的建设,它的结构是Intranet网的结构,它的构建思路是自上而下的,即首先把整个内部网看成是一个整体,这个整体的对象是网上所有用户,它必需有一个基础,这个基础为内网平台,就好象PC必需有一个操作系统为基础一样。内网平台负责所有用户对象的管理、负责所有网络资源(含网络应用)的管理、网络资源的分层授权、网络资源的开放标准和提供常用的网络服务(如邮件、论坛、导航、检索和公告等)。在平台的基础之上,插接各种业务应用(可理解为传统的MIS),这些应用都是网络资源。用户通过统一的浏览器界面入网,网络根据用户的权限提供相应的信息、功能和服务,使用户在网络环境下办公。

    OA系统产品选型

    一、明确研发公司实力
    一款优秀的产品背后必然有一个优秀的研发公司。对于OA产品而言更是如此,优秀的产品必然产生于优秀的理念,研发围绕着企业真实的需求和特点,能够在产品之中突出和体现对企业的个性化满足,并且能够在产品销售出去之后,仍然能够按时按质提供售后服务,让企业用户使用产品后得到切实的利益保障。所以,要衡量评估研发公司的实力,一方面可以通过现场评估,通过查看公司的成功案例和研发团队;另一方面,可以咨询已经有OA实施经验的企业,向他们了解相关的信息。OA是国内唯一与高校正式合作的产学研单位,借助了高校的科研力量,保持了持续的创新,具有较强的研发实力。同时结合了其他实施服务力量,具备强大的服务实力。
    二、明确产品的研发技术
    对于OA产品而言,它具有自身的特性,就是技术的快速更新和伴随企业发展而不断需要进行的扩展和升级。众所周知,现代竞争导致企业的发展节奏特别快,今天的管理模式与需求在明天可能已经显得有所不足,需要进行新管理模式的革新与探索,这就带来了新的问题,使企业有必要对原有的办公系统进行功能扩展和系统升级。由此,企业需要在选型过程中就要考虑到研发公司的技术是否具有足够的先进性,保证在一定的时期内不落时,在未来需要进行升级时能够做到顺利升级,不影响企业长远的发展需求。对于中小微组织,由于技术维护基础薄弱,选择与自身服务器技术统一的微软技术,是比较恰当的,协达轻量级ASP技术的产品,即定位于中小微组织。对于大型集团用户,选择JAVA技术和具有平台化属性的产品,最为恰当。
    三、试用体验产品的易用性
    虽然OA产品属于现代化程度较高,是企业管理升级和水平提升具有重要作用的办公工具,然而对于企业内部的各级管理者和员工而言,能否让他们快速成功掌握产品的使用,发挥办公系统的管理效能,使之与企业各级的管理者与职工的工作做到较好地融合,是充分发挥其作用的关键所在。所以,要在选型过程中选择实际试用产品,了解产品的设计细节是否具有足够的易用性,能够让企业职工快速上手,具有较好的用户体验。
    四、明确产品的性价比
    OA办公系统的引入需要企业安排专门的经费,对于一般的中小企业而言这往往需要一笔不小的费用,本着支出应当匹配相应回报的原则,在选型过程中要重视对OA产品性价比的了解。一方面,免费或者相比市场上同类其它产品价钱过低的产品不宜选择,因为必要的售后维护和产品不断升级使研发公司需要较大的费用投入,过低甚至免费显然是不现实的;另一方面,对于因附带了过多功能而导致费用太高的产品要慎重选择,毕竟企业看重的是综合的性价比,不需要太多不必要的功能模块浪费资源和资金。
    五、深入了解产品未来升级如何操作
    企业的发展使得产品需要不断进行更新和升级,特别是随着企业管理规模的扩大导致原有的系统难以满足新的需求,这时就需要对原办公系统进行功能扩展和升级的二次开发,这种开发对于不同的产品而言操作技术不同,需要的开发周期、资金、风险都存在较大的差异。像传统型与项目产品由于需要改动源代码进行二次开发,使其开发周期长、风险高、费用支出大,而新型的平台型OA办公系统则由于开发方式灵活,借助支撑平台技术可以在不影响原系统的情况下,独立开发新功能模块再加入到原系统平台之中,使得开发周期大大缩短、费用低、风险小,可以说这种升级操作最大程度上做到了为企业考虑,提供长远保证。
    OA办公系统遍地开花,各说各的好,各有各的理。乱花丛中如何慧眼识英雄,明辨高与低,找出最适合自己的那个“高帅富”?

    OA系统考虑因素

    在升级企业办公自动化系统时,必须考虑各种可以影响自动化效率的各种因素。这些因素包括预算,通信基础设施的变化等。但有两个因素是必须考虑的,人才的培养和办公场所的选择:
    与办公自动化相关的人员,基本上包括了所有办公自动化的用户和自动化系统和工具的供应商。各种各样的人,包括软件和硬件工程师,管理信息科学家,管理人员,中级工人,秘书,这些人都在使用自动化习系统。变化会对一些工人有困难,但只有变化才能使一个团队更有竞争力。
    办公自动化的需要的工作场所往往涉及到预算和员工的生理机能、交流以及管理信息。设备、布线、培训、安全数据输入都需要资金还有空间。研究发现,如患有重复动作综合症的人,就不合适使用自动化产品。重复动作综合症是一种医学紊乱,这个与长期的键盘输入和坐姿有关系。同样,环境安全问题同样还包括公司的整体安全状况,这个与计算机的辐射有关。

    OA系统发展方向

    随着人们对OA的熟悉程度加深,传统OA如公文传递,人事,协作等功能已经不能满足企业日常办公需要,很多企业管理者对OA提出了新的要求,金融危机下,很多公司为了控制费用预算,希望把传统的费用报销流程整合到OA审批流程里,且希望有效管理资产,如资产的领用、流转、 报修、报废等管理过程也希望整合到OA流程中,还有诸如视频会议等等。
    目前对传统OA需求呼声最高的还是,希望能把OA扩展到手机上,公司管理者随时可以看到待审批事件。
    OA发展到如今,其内涵已经发生了根本的转变,从最初的文档管理发展成为企业的信息化中心平台,可以说是完成了从一个士兵到将军的转变。当然,这个转变从理念上说已经完成,但从应用上来说还刚刚开始,我们可以肯定的说,OA平台化的时代已经到来。
    OA之所以要向平台化的方向发展,就是因为OA的作用正从行政管理转移到行政、业务兼管,从沟通转移到协作,从单一应用转移到系统整合。在OA行业,一直是需求推动技术的发展,OA这种转变正是这些年来乃至未来企业发展的重要需求所致。
    OA要想解决以上的问题,必须具有平台化的特征,这个平台必须具有充分的开放性和灵活性,允许用户方便的自定义各种业务流程和表单,和其他系统进行数据整合,生成各种统计报表。传统的PHP技术开发的OA因为不具有平台性而正在逐渐被淘汰,最具代表性的OA平台一般都是JAVA技术开发,在这种OA平台上,在业务中做沟通,而不是在沟通中做业务,沟通、业务、组织、管理都是协同一致的。
    以目前市场覆盖面最广的OA系统为例,概括为以下几个特点。
    一个平台:
    统一的基础管理平台,实现用户数据统一管理、权限统一分配、身份统一认证。
    两个门户:
    统一规划门户网站群和协同办公平台,将外网信息维护、客户服务、互动交流和日常工作紧密结合起来,有效提高工作效率。
    集团化管理:
    应用对象覆盖多级机构,实现“大OA套小OA”的应用模式。
    四大应用:
    工作流程、知识管理、沟通交流和辅助办公四大核心应用。

    摘自 : 百度百科

    展开全文
  • 汽车操作系统的前世今生

    千次阅读 2020-03-17 14:18:28
    随着阿里巴巴Alios和百度Apollo计划的轮番登台,“操作系统OperatingSystem” 似乎在一夜间成为了智能网联汽车的标配。事实真是如此吗?本文将简单的介绍下汽车操作系统的前世今生。 1、车载系统和电控系统 要谈汽车...
  • 中国中小管理咨询公司现状

    万次阅读 2013-10-19 00:34:04
    看到世界经理人社区发起一个世界经理人咨询公会, 我认为这可能已经颠覆了现代中国咨询业的传统业务模式! 因此, 我们有必要探讨一下我国的中小管理咨询公司的现状. 首先需要对管理咨询有一个大概的了解。本将解释管理...
  • 智慧城市系列之智能交通系统(ITS)

    千次阅读 2020-09-14 09:44:42
    第四章ITS的主要内容 ITS的基本功能表现在:减少出行时间、保障交通安全、缓解交通拥挤...考虑到系统在国外、国内投入运营的情况,这里对前四个子系统进行重点介绍,并结合各子系统的特点,选择不同的侧重点分别予...
  • 支付清算系统是经济金融活动的基础性支撑。支付、清算体系建设是金融理论与实践的重点课题。本文主要描述了支付系统建设的发展历程及构成,分析了银行内部清算体系设计,有利于快速建立起金融服务体系思路。希望略尽...
  • ERP咨询顾问应该具备的基本素质

    千次阅读 2012-01-06 14:48:34
    ERP顾问是企业实施ERP系统的一股强大的外部推动力,企业在实施ERP项目的过程中,顾问起到了相当重要的作用。对于企业而言,在信息化的过程中依靠有实务经验的顾问指引方向或提供建议,可以省掉许多不必要的摸索或...
  • 整形医院咨询接待回答技巧

    千次阅读 2011-05-06 14:02:00
    也许这是范本,也许这有些呆板,但却是大纲,作为咨询你所回答的每一个问题也许都会在这里面体现到,你的一言一语关系到你在电话咨询顾客中的每一印象,关系到医院在她们心中的形象,所以咨询者(不管是现场还是电话)...
  • BOSS系统的流程与运作

    千次阅读 2011-04-11 22:58:00
    广东移动BOSS系统的... 营业分系统 营业分系统又可进一步分为营业受理子系统、营业管理子系统、客户服务子系统。营业受理子系统直接面对移动客户,主要进行移动运营商直辖的各大营业厅及代销点的营业
  • 中国管理咨询产业发展的分析

    千次阅读 2008-01-22 13:11:00
    管理咨询产业发展分析中国管理咨询产业发展的分析第一章:管理咨询产业发展历史一、欧美管理咨询产业的发展历史 管理咨询是帮助企业和企业家,通过解决管理和经营问题,鉴别和抓住新机会,强化学习和实施变革以实现...
  • 系统设计入门

    万次阅读 2018-01-13 10:46:55
    译文地址:系统设计入门 译文出自:掘金翻译计划 译者:XatMassacrE、L9m、Airmacho、xiaoyusilen、jifaxu 这个 链接 用来查看本翻译与英文版是否有差别(如果你没有看到 README.md 发生变化,那就意味着这份...
  • ISO9000质量认证咨询服务过程

    千次阅读 2009-12-09 18:00:00
    ISO9000质量认证咨询服务过程 1、准备阶段 1. 1诊断:了解企业现状,确认企业管理优势,找出薄弱环节,与ISO9000系列国际标准要求之间的差距,向企业提交诊断报告。 1.2工作计划:指导企业起草认证工作计划,双方确认...
  • 一个ERP咨询顾问的九年经验总结

    千次阅读 2011-09-15 10:14:36
    自进入ERP咨询实施行业以来,一直都在不断地学习、实践、总结,不断地在项目中经历着痛苦抑或者快乐的磨砺和蜕变,‘痛并快乐着’是我很长一段时间以来的真实感受。从一个懵懂而又充满激情的青年学生,经过技术支持...
  • 伺服系统(自动控制系统

    万次阅读 2015-01-03 15:03:21
    伺服系统(自动控制系统) 伺服系统(servomechanism)又称随动系统,是用来精确地跟随或复现某个过程的反馈控制系统。伺服系统使物体的位置、方位、状态等输出被控量能够跟随输入目标(或给定值)的任意变化的自动...
  • 冷眼旁观:解析IT咨询

    千次阅读 2005-07-31 12:35:00
    为什么会有这么多的企业试图进入IT咨询领域呢?原因很简单,那就是这个市场的高速增长和巨大的利润空间。 可以预见,不论是用友、金蝶还是国内的其他软件产品,如果想在市场竞争中获得成功,那么软件公司就必须逐步...
  • 企业BI系统应用的切入点及五大策略

    千次阅读 多人点赞 2017-02-08 13:47:56
    从实施的角度来出发,实施商业智能系统是一项复杂的系统工程,整个项目涉及企业管理,运作管理,信息系统,数据仓库,数据挖掘,统计分析等众多门类的知识. 因此用户除了管理咨询要选择合适的商业智能软件工具外还...
  • 2006年信息产业部ERP管理咨询师试题

    千次阅读 2007-06-25 08:53:00
    闭卷部分(考试时间1小时)一、 多选题(共5题)1、IT管理...A、信息化需求及定位咨询 B、人力资源管理咨询C、各类应用解决方案咨询 D、业务运营模式咨询E、ERP咨询2、波士顿分析模型中包含那些因素? A、金牛 
  • ERP系统

    千次阅读 2018-10-26 00:55:49
    ERP系统是企业资源计划(Enterprise Resource Planning )的简称,是指建立在信息技术基础上,集信息技术与先进管理思想于一身,以系统化的管理思想,为企业员工及决策层提供决策手段的管理平台。它是从MRP(物料需求...
  • 机器学习系统设计:Python 语言实现

    千次阅读 多人点赞 2018-08-01 10:19:57
    从设计的角度来审视这些系统,我们能够深入理解其底层算法和可用的优化方法。本书为我们提供了机器学习设计过程的坚实基础,能够使我们为特定问题建立起定制的机器学习模型。我们可能已经了解或使用过一些为解决常见...
  • ### 如何系统化学习OpenCV4

    千次阅读 2019-08-17 11:38:17
    如何系统化学习OpenCV4 OpenCV4.0发布以来,其依靠良好的接口代码、系统级别的优化、更加通用易学的函数调用,集成OpenVINO与tensorflow、caffe等模型加速推断、实现了从传统的图像处理到基于深度学习的视觉处理路线...
  • 系统集成

    千次阅读 2011-08-22 00:39:27
    一、什么是系统集成?  系统集成,英文System Integration,指一个组织机构内的设备、信息的集成,并通过完整地系统来实现对应用的支持。系统集成包括设备系统集成和应用系统集成。  设备系统集成,也可称为...
  • 邮件系统

    千次阅读 2011-12-06 11:22:28
    邮件系统是一套单独的系统,要有自己的服务器,在邮件系统中可以设多个域,每个域中可以设多个用户,比如说我买了某邮件厂商提供的商业版本的的邮件系统或者是开源的邮件系统,可以进行多项设置.它的数据是放在自己的...
  •   [精彩] 进程及时知道父进程已经退出的最简单方案?http://www.chinaunix.net 作者:yuonunix 发表于:2003-10-31 10:14:14【发表评论】 【查看原文】 【C/C++讨论区】【关闭】 要父进程知道...
  • PetShop的系统架构第三篇

    千次阅读 2011-03-26 22:49:00
    前言:PetShop是一个范例,微软用它来展示.Net企业系统开发的能力。业界有许多.Net与J2EE之争,许多数据是从微软的PetShop和Sun的PetStore而来。这种争论不可避免带有浓厚的商业色彩,对于我们开发人员而言,...
  • 基于MES的生产管理系统应用

    千次阅读 2008-11-23 20:45:00
    介绍该系统的设计原理、软硬件结构图,着重分析说明计划管理系统的开发过程、技术关健及具体实现方法和运行结果。该系统为某航空企业的车间实施计算机管理、提高生产效率提供了有效的工具。引 言 随着信息技术的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 18,843
精华内容 7,537
关键字:

咨询子系统的作用