// template version 1.15
using namespace std;
#include <bits/stdc++.h>

// varibable settings
#define int long long
const int INF=1e18;

// define basic macro {{{
#define _overload3(_1,_2,_3,name,...) name
#define _rep(i,n) repi(i,0,n)
#define repi(i,a,b) for(int i=(int)(a);i<(int)(b);++i)
#define rep(...) _overload3(__VA_ARGS__,repi,_rep,)(__VA_ARGS__)
#define _rrep(i,n) rrepi(i,0,n)
#define rrepi(i,a,b) for(int i=(int)((b)-1);i>=(int)(a);--i)
#define rrep(...) _overload3(__VA_ARGS__,rrepi,_rrep,)(__VA_ARGS__)
#define each(i,a) for (auto&& i : a)
#define all(x) (x).begin(),(x).end()
#define sz(x) ((int)(x).size())
#define pb(a) push_back(a)
#define mp(a, b) make_pair(a, b)
#define mt(a, b, c) make_tuple(a, b, c)
#define divceil(a,b) ((a)+(b)-1)/(b)
#define is_in(x, a, b) ((a)<=(x) && (x)<(b))
#define uni(x) sort(all(x));x.erase(unique(all(x)),x.end())
#define ub upper_bound
#define lb lower_bound
#define posl(A, x) (lower_bound(all(A), x)-A.begin())
#define posu(A, x) (upper_bound(all(A),x)-A.begin())
template<class T> inline void chmax(T &a, const T &b) { if((a) < (b)) (a) = (b); }
template<class T> inline void chmin(T &a, const T &b) { if((a) > (b)) (a) = (b); }

typedef long long ll;
typedef vector<int> vi;
typedef vector<vi> vvi;
typedef long double ld;
typedef pair<int,int> pii;
typedef tuple<int,int,int> iii;

template<typename T> using PQ = priority_queue<T, vector<T>, greater<T>>;
struct Fast { Fast(){ std::cin.tie(0); ios::sync_with_stdio(false); } } fast;

#if defined(PCM) || defined(LOCAL)
  #include "lib/dump.hpp"
#else
  #define dump(...) 42
  #define dump_1d(...) 42
  #define dump_2d(...) 42
#endif
//}}}

using Cost = int;  // TODO: update
Cost zerocost = 0LL;  // TODO: update
Cost infcost = INF;  // TODO: update
using Pos = int;  // TODO: update
using EDGE = pair<Pos, Cost>;

struct Graph{
  map<Pos, vector<EDGE>> all_edges;
  struct Dist {
    map<Pos, Cost> data;
    Cost operator[](Pos pos){
      if (data.find(pos)!=data.end()) return data[pos];
      else                      return infcost;
    }
  };

  void add_edge(Pos from, Pos to, Cost cost){
    all_edges[from].emplace_back(mp(to, cost));
  }

  auto operator[](Pos pos){
    return all_edges[pos];
  }

  friend ostream& operator<<(ostream& os, Graph& G){
    os << G.all_edges;
    return os;
  }

  Dist dijkstra(Pos start){
    Dist d;  // 最短距離
    PQ<pair<Cost, Pos>> pq;
    pq.push(mp(zerocost, start));
    while (!pq.empty()){
      auto cp = pq.top(); pq.pop();
      auto cost = cp.first;
      auto pos = cp.second;
      if (cost < d[pos]) {
        d.data[pos] = cost;
        for (const auto &el: all_edges[pos]){
          int npos = el.first;
          int ncost = cost+el.second;
          if (ncost < d[npos]) pq.push(mp(ncost, npos));
        }
      }
    }
    return d;
  }
};



signed main() {
  int h,w;cin>>h>>w;
  int n = h*w;
  vvi block(h, vi(w));
  rep(i, h){
    string s;cin>>s;
    rep(j, w){
      block[i][j] = (s[j]=='.'?0:1);
    }
  }

  vvi g(n);
  Graph G;

  int di[]={1, -1, 0, 0};
  int dj[]={0, 0, 1, -1};
  rep(i, h)rep(j, w){
    if (block[i][j]) continue;
    int u = i*w+j;

    rep(k, 4){
      int ni = i+di[k];
      int nj = j+dj[k];
      int v = ni*w + nj;
      if (is_in(ni, 0, h) && is_in(nj, 0, w) && !block[ni][nj]){
        g[u].pb(v); // 自分から生える辺だけでよい。そうしないと二重辺になってしまう。
        G.add_edge(u, v, 1);
      }
    }
  }
  dump(G);
  
  int ans = 1;
  rep(i, h)rep(j, w){
    if (block[i][j]) continue;

    int u = i*w+j;
    auto d = G.dijkstra(u);
    rep(i, n) chmax(ans, (d[i]==INF ? 1 : d[i]));
  }
  cout << ans << endl;

  return 0;
}
